From b327da7092b2ae3af360064482da9b4d3ae88516 Mon Sep 17 00:00:00 2001 From: Uleat Date: Thu, 24 Mar 2016 18:50:31 -0400 Subject: [PATCH] Activation of the new 'Bots' command system --- changelog.txt | 5 + common/classes.cpp | 841 +- common/classes.h | 142 +- common/database.h | 4 +- common/eq_constants.h | 92 +- common/item.cpp | 4 +- common/patches/ss_define.h | 23 + common/races.cpp | 910 +- common/races.h | 186 +- common/ruletypes.h | 19 + common/shareddb.cpp | 38 +- common/shareddb.h | 23 +- common/spdat.h | 6 +- common/version.h | 4 +- utils/scripts/opcode_handlers.py | 1 + .../sql/git/bots/bots_db_update_manifest.txt | 2 + .../2016_03_24_bots_command_rules.sql | 5 + .../2016_03_24_bots_command_settings.sql | 102 + world/client.cpp | 20 +- world/clientlist.cpp | 9 +- world/eqw.cpp | 6 +- zone/CMakeLists.txt | 6 + zone/aa.cpp | 4 +- zone/bot.cpp | 5245 ++---------- zone/bot.h | 221 +- zone/bot_command.cpp | 7312 +++++++++++++++++ zone/bot_command.h | 667 ++ zone/bot_database.cpp | 732 ++ zone/bot_database.h | 87 + zone/bot_structs.h | 19 + zone/botspellsai.cpp | 26 +- zone/client.cpp | 32 +- zone/client_mods.cpp | 6 +- zone/client_packet.cpp | 3 +- zone/command.cpp | 48 +- zone/command.h | 2 +- zone/embparser.cpp | 10 +- zone/embxs.cpp | 30 +- zone/entity.h | 3 +- zone/groups.cpp | 62 +- zone/groups.h | 10 +- zone/heal_rotation.cpp | 882 ++ zone/heal_rotation.h | 148 + zone/inventory.cpp | 7 +- zone/mob.cpp | 374 +- zone/mob.h | 25 +- zone/net.cpp | 30 +- zone/worldserver.cpp | 4 +- 48 files changed, 12821 insertions(+), 5616 deletions(-) create mode 100644 utils/sql/git/bots/required/2016_03_24_bots_command_rules.sql create mode 100644 utils/sql/git/bots/required/2016_03_24_bots_command_settings.sql create mode 100644 zone/bot_command.cpp create mode 100644 zone/bot_command.h create mode 100644 zone/bot_database.cpp create mode 100644 zone/bot_database.h create mode 100644 zone/heal_rotation.cpp create mode 100644 zone/heal_rotation.h diff --git a/changelog.txt b/changelog.txt index cd25aa82c..174196c4f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,11 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) == 03/24/2016 == Kayen: Fix for AE taunt to use correct range and hate modifier. Fix for spell effect version of taunt to use correct range. +Uleat: Activation of new 'Bots' command system + - You will need to re-run cmake to capture the file additions and then re-compile your server binaries + - You will also need to manually run eqemu_update.pl and select the bots update option - you should have 2 pending updates: 9001 & 9002 + - The new command system is accessed with the '^' character - start with '^help' and see where that takes you + - More information can be found on the eqemu forums == 03/05/2016 == mackal: Implement extra bind points (secondary recall) diff --git a/common/classes.cpp b/common/classes.cpp index 1f54c9234..58aa56602 100644 --- a/common/classes.cpp +++ b/common/classes.cpp @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemu.org) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,275 +18,576 @@ #include "../common/global_define.h" #include "../common/classes.h" -const char* GetEQClassName(uint8 class_, uint8 level) { - switch(class_) { - case WARRIOR: - if (level >= 70) - return "Vanquisher"; - else if (level >= 65) - return "Overlord"; //Baron-Sprite: LEAVE MY CLASSES ALONE. - else if (level >= 60) - return "Warlord"; - else if (level >= 55) - return "Myrmidon"; - else if (level >= 51) - return "Champion"; - else - return "Warrior"; - case CLERIC: - if (level >= 70) - return "Prelate"; - else if (level >= 65) - return "Archon"; - else if (level >= 60) - return "High Priest"; - else if (level >= 55) - return "Templar"; - else if (level >= 51) - return "Vicar"; - else - return "Cleric"; - case PALADIN: - if (level >= 70) - return "Lord"; - else if (level >= 65) - return "Lord Protector"; - else if (level >= 60) - return "Crusader"; - else if (level >= 55) - return "Knight"; - else if (level >= 51) - return "Cavalier"; - else - return "Paladin"; - case RANGER: - if (level >= 70) - return "Plainswalker"; - else if (level >= 65) - return "Forest Stalker"; - else if (level >= 60) - return "Warder"; - else if (level >= 55) - return "Outrider"; - else if (level >= 51) - return "Pathfinder"; - else - return "Ranger"; - case SHADOWKNIGHT: - if (level >= 70) - return "Scourge Knight"; - else if (level >= 65) - return "Dread Lord"; - else if (level >= 60) - return "Grave Lord"; - else if (level >= 55) - return "Revenant"; - else if (level >= 51) - return "Reaver"; - else - return "Shadowknight"; - case DRUID: - if (level >= 70) - return "Natureguard"; - else if (level >= 65) - return "Storm Warden"; - else if (level >= 60) - return "Hierophant"; - else if (level >= 55) - return "Preserver"; - else if (level >= 51) - return "Wanderer"; - else - return "Druid"; - case MONK: - if (level >= 70) - return "Stone Fist"; - else if (level >= 65) - return "Transcendent"; - else if (level >= 60) - return "Grandmaster"; - else if (level >= 55) - return "Master"; - else if (level >= 51) - return "Disciple"; - else - return "Monk"; - case BARD: - if (level >= 70) - return "Performer"; - else if (level >= 65) - return "Maestro"; - else if (level >= 60) - return "Virtuoso"; - else if (level >= 55) - return "Troubadour"; - else if (level >= 51) - return "Minstrel"; - else - return "Bard"; - case ROGUE: - if (level >= 70) - return "Nemesis"; - else if (level >= 65) - return "Deceiver"; - else if (level >= 60) - return "Assassin"; - else if (level >= 55) - return "Blackguard"; - else if (level >= 51) - return "Rake"; - else - return "Rogue"; - case SHAMAN: - if (level >= 70) - return "Soothsayer"; - else if (level >= 65) - return "Prophet"; - else if (level >= 60) - return "Oracle"; - else if (level >= 55) - return "Luminary"; - else if (level >= 51) - return "Mystic"; - else - return "Shaman"; - case NECROMANCER: - if (level >= 70) - return "Wraith"; - else if (level >= 65) - return "Arch Lich"; - else if (level >= 60) - return "Warlock"; - else if (level >= 55) - return "Defiler"; - else if (level >= 51) - return "Heretic"; - else - return "Necromancer"; - case WIZARD: - if (level >= 70) - return "Grand Arcanist"; - else if (level >= 65) - return "Arcanist"; - else if (level >= 60) - return "Sorcerer"; - else if (level >= 55) - return "Evoker"; - else if (level >= 51) - return "Channeler"; - else - return "Wizard"; - case MAGICIAN: - if (level >= 70) - return "Arch Magus"; - else if (level >= 65) - return "Arch Convoker"; - else if (level >= 60) - return "Arch Mage"; - else if (level >= 55) - return "Conjurer"; - if (level >= 51) - return "Elementalist"; - else - return "Magician"; - case ENCHANTER: - if (level >= 70) - return "Bedazzler"; - else if (level >= 65) - return "Coercer"; - else if (level >= 60) - return "Phantasmist"; - else if (level >= 55) - return "Beguiler"; - else if (level >= 51) - return "Illusionist"; - else - return "Enchanter"; - case BEASTLORD: - if (level >= 70) - return "Wildblood"; - else if (level >= 65) - return "Feral Lord"; - else if (level >= 60) - return "Savage Lord"; - else if (level >= 55) - return "Animist"; - else if (level >= 51) - return "Primalist"; - else - return "Beastlord"; - case BERSERKER: - if (level >= 70) - return "Ravager"; - else if (level >= 65) - return "Fury"; - else if (level >= 60) - return "Rager"; - else if (level >= 55) - return "Vehement"; - else if (level >= 51) - return "Brawler"; - else - return "Berserker"; - case BANKER: - if (level >= 70) - return "Master Banker"; - else if (level >= 65) - return "Elder Banker"; - else if (level >= 60) - return "Oldest Banker"; - else if (level >= 55) - return "Older Banker"; - else if (level >= 51) - return "Old Banker"; - else - return "Banker"; - case WARRIORGM: - return "Warrior Guildmaster"; - case CLERICGM: - return "Cleric Guildmaster"; - case PALADINGM: - return "Paladin Guildmaster"; - case RANGERGM: - return "Ranger Guildmaster"; - case SHADOWKNIGHTGM: - return "Shadowknight Guildmaster"; - case DRUIDGM: - return "Druid Guildmaster"; - case MONKGM: - return "Monk Guildmaster"; - case BARDGM: - return "Bard Guildmaster"; - case ROGUEGM: - return "Rogue Guildmaster"; - case SHAMANGM: - return "Shaman Guildmaster"; - case NECROMANCERGM: - return "Necromancer Guildmaster"; - case WIZARDGM: - return "Wizard Guildmaster"; - case MAGICIANGM: - return "Magician Guildmaster"; - case ENCHANTERGM: - return "Enchanter Guildmaster"; - case BEASTLORDGM: - return "Beastlord Guildmaster"; - case BERSERKERGM: - return "Berserker Guildmaster"; - case MERCHANT: - return "Merchant"; - case ADVENTURERECRUITER: - return "Adventure Recruiter"; - case ADVENTUREMERCHANT: - return "Adventure Merchant"; - case CORPSE_CLASS: - return "Corpse Class"; - case TRIBUTE_MASTER: - return "Tribute Master"; - case GUILD_TRIBUTE_MASTER: - return "Guild Tribute Master"; - default: - return "Unknown"; +const char* GetClassIDName(uint8 class_id, uint8 level) +{ + switch (class_id) { + case WARRIOR: + if (level >= 70) + return "Vanquisher"; + else if (level >= 65) + return "Overlord"; //Baron-Sprite: LEAVE MY CLASSES ALONE. + else if (level >= 60) + return "Warlord"; + else if (level >= 55) + return "Myrmidon"; + else if (level >= 51) + return "Champion"; + else + return "Warrior"; + case CLERIC: + if (level >= 70) + return "Prelate"; + else if (level >= 65) + return "Archon"; + else if (level >= 60) + return "High Priest"; + else if (level >= 55) + return "Templar"; + else if (level >= 51) + return "Vicar"; + else + return "Cleric"; + case PALADIN: + if (level >= 70) + return "Lord"; + else if (level >= 65) + return "Lord Protector"; + else if (level >= 60) + return "Crusader"; + else if (level >= 55) + return "Knight"; + else if (level >= 51) + return "Cavalier"; + else + return "Paladin"; + case RANGER: + if (level >= 70) + return "Plainswalker"; + else if (level >= 65) + return "Forest Stalker"; + else if (level >= 60) + return "Warder"; + else if (level >= 55) + return "Outrider"; + else if (level >= 51) + return "Pathfinder"; + else + return "Ranger"; + case SHADOWKNIGHT: + if (level >= 70) + return "Scourge Knight"; + else if (level >= 65) + return "Dread Lord"; + else if (level >= 60) + return "Grave Lord"; + else if (level >= 55) + return "Revenant"; + else if (level >= 51) + return "Reaver"; + else + return "Shadowknight"; + case DRUID: + if (level >= 70) + return "Natureguard"; + else if (level >= 65) + return "Storm Warden"; + else if (level >= 60) + return "Hierophant"; + else if (level >= 55) + return "Preserver"; + else if (level >= 51) + return "Wanderer"; + else + return "Druid"; + case MONK: + if (level >= 70) + return "Stone Fist"; + else if (level >= 65) + return "Transcendent"; + else if (level >= 60) + return "Grandmaster"; + else if (level >= 55) + return "Master"; + else if (level >= 51) + return "Disciple"; + else + return "Monk"; + case BARD: + if (level >= 70) + return "Performer"; + else if (level >= 65) + return "Maestro"; + else if (level >= 60) + return "Virtuoso"; + else if (level >= 55) + return "Troubadour"; + else if (level >= 51) + return "Minstrel"; + else + return "Bard"; + case ROGUE: + if (level >= 70) + return "Nemesis"; + else if (level >= 65) + return "Deceiver"; + else if (level >= 60) + return "Assassin"; + else if (level >= 55) + return "Blackguard"; + else if (level >= 51) + return "Rake"; + else + return "Rogue"; + case SHAMAN: + if (level >= 70) + return "Soothsayer"; + else if (level >= 65) + return "Prophet"; + else if (level >= 60) + return "Oracle"; + else if (level >= 55) + return "Luminary"; + else if (level >= 51) + return "Mystic"; + else + return "Shaman"; + case NECROMANCER: + if (level >= 70) + return "Wraith"; + else if (level >= 65) + return "Arch Lich"; + else if (level >= 60) + return "Warlock"; + else if (level >= 55) + return "Defiler"; + else if (level >= 51) + return "Heretic"; + else + return "Necromancer"; + case WIZARD: + if (level >= 70) + return "Grand Arcanist"; + else if (level >= 65) + return "Arcanist"; + else if (level >= 60) + return "Sorcerer"; + else if (level >= 55) + return "Evoker"; + else if (level >= 51) + return "Channeler"; + else + return "Wizard"; + case MAGICIAN: + if (level >= 70) + return "Arch Magus"; + else if (level >= 65) + return "Arch Convoker"; + else if (level >= 60) + return "Arch Mage"; + else if (level >= 55) + return "Conjurer"; + if (level >= 51) + return "Elementalist"; + else + return "Magician"; + case ENCHANTER: + if (level >= 70) + return "Bedazzler"; + else if (level >= 65) + return "Coercer"; + else if (level >= 60) + return "Phantasmist"; + else if (level >= 55) + return "Beguiler"; + else if (level >= 51) + return "Illusionist"; + else + return "Enchanter"; + case BEASTLORD: + if (level >= 70) + return "Wildblood"; + else if (level >= 65) + return "Feral Lord"; + else if (level >= 60) + return "Savage Lord"; + else if (level >= 55) + return "Animist"; + else if (level >= 51) + return "Primalist"; + else + return "Beastlord"; + case BERSERKER: + if (level >= 70) + return "Ravager"; + else if (level >= 65) + return "Fury"; + else if (level >= 60) + return "Rager"; + else if (level >= 55) + return "Vehement"; + else if (level >= 51) + return "Brawler"; + else + return "Berserker"; + case BANKER: + if (level >= 70) + return "Master Banker"; + else if (level >= 65) + return "Elder Banker"; + else if (level >= 60) + return "Oldest Banker"; + else if (level >= 55) + return "Older Banker"; + else if (level >= 51) + return "Old Banker"; + else + return "Banker"; + case WARRIORGM: + return "Warrior Guildmaster"; + case CLERICGM: + return "Cleric Guildmaster"; + case PALADINGM: + return "Paladin Guildmaster"; + case RANGERGM: + return "Ranger Guildmaster"; + case SHADOWKNIGHTGM: + return "Shadowknight Guildmaster"; + case DRUIDGM: + return "Druid Guildmaster"; + case MONKGM: + return "Monk Guildmaster"; + case BARDGM: + return "Bard Guildmaster"; + case ROGUEGM: + return "Rogue Guildmaster"; + case SHAMANGM: + return "Shaman Guildmaster"; + case NECROMANCERGM: + return "Necromancer Guildmaster"; + case WIZARDGM: + return "Wizard Guildmaster"; + case MAGICIANGM: + return "Magician Guildmaster"; + case ENCHANTERGM: + return "Enchanter Guildmaster"; + case BEASTLORDGM: + return "Beastlord Guildmaster"; + case BERSERKERGM: + return "Berserker Guildmaster"; + case MERCHANT: + return "Merchant"; + case ADVENTURERECRUITER: + return "Adventure Recruiter"; + case ADVENTUREMERCHANT: + return "Adventure Merchant"; + case CORPSE_CLASS: + return "Corpse Class"; + case TRIBUTE_MASTER: + return "Tribute Master"; + case GUILD_TRIBUTE_MASTER: + return "Guild Tribute Master"; + default: + return "Unknown"; } } +const char* GetPlayerClassName(uint32 player_class_value, uint8 level) +{ + return GetClassIDName(GetClassIDFromPlayerClassValue(player_class_value), level); +} + +uint32 GetPlayerClassValue(uint8 class_id) +{ + switch (class_id) { + case WARRIOR: + case CLERIC: + case PALADIN: + case RANGER: + case SHADOWKNIGHT: + case DRUID: + case MONK: + case BARD: + case ROGUE: + case SHAMAN: + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + case BEASTLORD: + case BERSERKER: + return class_id; + default: + return PLAYER_CLASS_UNKNOWN; // watch + } +} + +uint32 GetPlayerClassBit(uint8 class_id) +{ + switch (class_id) { + case WARRIOR: + return PLAYER_CLASS_WARRIOR_BIT; + case CLERIC: + return PLAYER_CLASS_CLERIC_BIT; + case PALADIN: + return PLAYER_CLASS_PALADIN_BIT; + case RANGER: + return PLAYER_CLASS_RANGER_BIT; + case SHADOWKNIGHT: + return PLAYER_CLASS_SHADOWKNIGHT_BIT; + case DRUID: + return PLAYER_CLASS_DRUID_BIT; + case MONK: + return PLAYER_CLASS_MONK_BIT; + case BARD: + return PLAYER_CLASS_BARD_BIT; + case ROGUE: + return PLAYER_CLASS_ROGUE_BIT; + case SHAMAN: + return PLAYER_CLASS_SHAMAN_BIT; + case NECROMANCER: + return PLAYER_CLASS_NECROMANCER_BIT; + case WIZARD: + return PLAYER_CLASS_WIZARD_BIT; + case MAGICIAN: + return PLAYER_CLASS_MAGICIAN_BIT; + case ENCHANTER: + return PLAYER_CLASS_ENCHANTER_BIT; + case BEASTLORD: + return PLAYER_CLASS_BEASTLORD_BIT; + case BERSERKER: + return PLAYER_CLASS_BERSERKER_BIT; + default: + return PLAYER_CLASS_UNKNOWN_BIT; + } +} + +uint8 GetClassIDFromPlayerClassValue(uint32 player_class_value) +{ + switch (player_class_value) { + case PLAYER_CLASS_WARRIOR: + case PLAYER_CLASS_CLERIC: + case PLAYER_CLASS_PALADIN: + case PLAYER_CLASS_RANGER: + case PLAYER_CLASS_SHADOWKNIGHT: + case PLAYER_CLASS_DRUID: + case PLAYER_CLASS_MONK: + case PLAYER_CLASS_BARD: + case PLAYER_CLASS_ROGUE: + case PLAYER_CLASS_SHAMAN: + case PLAYER_CLASS_NECROMANCER: + case PLAYER_CLASS_WIZARD: + case PLAYER_CLASS_MAGICIAN: + case PLAYER_CLASS_ENCHANTER: + case PLAYER_CLASS_BEASTLORD: + case PLAYER_CLASS_BERSERKER: + return player_class_value; + default: + return PLAYER_CLASS_UNKNOWN; // watch + } +} + +uint8 GetClassIDFromPlayerClassBit(uint32 player_class_bit) +{ + switch (player_class_bit) { + case PLAYER_CLASS_WARRIOR_BIT: + return WARRIOR; + case PLAYER_CLASS_CLERIC_BIT: + return CLERIC; + case PLAYER_CLASS_PALADIN_BIT: + return PALADIN; + case PLAYER_CLASS_RANGER_BIT: + return RANGER; + case PLAYER_CLASS_SHADOWKNIGHT_BIT: + return SHADOWKNIGHT; + case PLAYER_CLASS_DRUID_BIT: + return DRUID; + case PLAYER_CLASS_MONK_BIT: + return MONK; + case PLAYER_CLASS_BARD_BIT: + return BARD; + case PLAYER_CLASS_ROGUE_BIT: + return ROGUE; + case PLAYER_CLASS_SHAMAN_BIT: + return SHAMAN; + case PLAYER_CLASS_NECROMANCER_BIT: + return NECROMANCER; + case PLAYER_CLASS_WIZARD_BIT: + return WIZARD; + case PLAYER_CLASS_MAGICIAN_BIT: + return MAGICIAN; + case PLAYER_CLASS_ENCHANTER_BIT: + return ENCHANTER; + case PLAYER_CLASS_BEASTLORD_BIT: + return BEASTLORD; + case PLAYER_CLASS_BERSERKER_BIT: + return BERSERKER; + default: + return PLAYER_CLASS_UNKNOWN; // watch + } +} + +bool IsFighterClass(uint8 class_id) +{ + switch (class_id) { + case WARRIOR: + case PALADIN: + case RANGER: + case SHADOWKNIGHT: + case MONK: + case BARD: + case ROGUE: + case BEASTLORD: + case BERSERKER: + return true; + default: + return false; + } +} + +bool IsSpellFighterClass(uint8 class_id) +{ + switch (class_id) { + case PALADIN: + case RANGER: + case SHADOWKNIGHT: + case BEASTLORD: + return true; + default: + return false; + } +} + +bool IsNonSpellFighterClass(uint8 class_id) +{ + switch (class_id) { + case WARRIOR: + case MONK: + case BARD: + case ROGUE: + case BERSERKER: + return true; + default: + return false; + } +} + +bool IsCasterClass(uint8 class_id) +{ + switch (class_id) { + case CLERIC: + case DRUID: + case SHAMAN: + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + return true; + default: + return false; + } +} + +bool IsINTCasterClass(uint8 class_id) +{ + switch (class_id) { + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + return true; + default: + return false; + } +} + +bool IsWISCasterClass(uint8 class_id) +{ + switch (class_id) { + case CLERIC: + case DRUID: + case SHAMAN: + return true; + default: + return false; + } +} + +bool IsPlateClass(uint8 class_id) +{ + switch (class_id) { + case WARRIOR: + case CLERIC: + case PALADIN: + case SHADOWKNIGHT: + case BARD: + return true; + default: + return false; + } +} + +bool IsChainClass(uint8 class_id) +{ + switch (class_id) { + case RANGER: + case ROGUE: + case SHAMAN: + case BERSERKER: + return true; + default: + return false; + } +} + +bool IsLeatherClass(uint8 class_id) +{ + switch (class_id) { + case DRUID: + case MONK: + case BEASTLORD: + return true; + default: + return false; + } +} + +bool IsClothClass(uint8 class_id) +{ + switch (class_id) { + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + return true; + default: + return false; + } +} + +uint8 ClassArmorType(uint8 class_id) +{ + switch (class_id) { + case WARRIOR: + case CLERIC: + case PALADIN: + case SHADOWKNIGHT: + case BARD: + return ARMOR_TYPE_PLATE; + case RANGER: + case ROGUE: + case SHAMAN: + case BERSERKER: + return ARMOR_TYPE_CHAIN; + case DRUID: + case MONK: + case BEASTLORD: + return ARMOR_TYPE_LEATHER; + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + return ARMOR_TYPE_CLOTH; + default: + return ARMOR_TYPE_UNKNOWN; + } +} diff --git a/common/classes.h b/common/classes.h index c2f0c8acf..2ca9a3c4d 100644 --- a/common/classes.h +++ b/common/classes.h @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemu.org) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,26 +17,25 @@ */ #ifndef CLASSES_CH #define CLASSES_CH + #include "../common/types.h" -#define Array_Class_UNKNOWN 0 -#define WARRIOR 1 -#define CLERIC 2 -#define PALADIN 3 -#define RANGER 4 -#define SHADOWKNIGHT 5 -#define DRUID 6 -#define MONK 7 -#define BARD 8 -#define ROGUE 9 -#define SHAMAN 10 -#define NECROMANCER 11 -#define WIZARD 12 -#define MAGICIAN 13 -#define ENCHANTER 14 -#define BEASTLORD 15 -#define BERSERKER 16 -#define PLAYER_CLASS_COUNT 16 // used for array defines, must be the count of playable classes +#define WARRIOR 1 +#define CLERIC 2 +#define PALADIN 3 +#define RANGER 4 +#define SHADOWKNIGHT 5 +#define DRUID 6 +#define MONK 7 +#define BARD 8 +#define ROGUE 9 +#define SHAMAN 10 +#define NECROMANCER 11 +#define WIZARD 12 +#define MAGICIAN 13 +#define ENCHANTER 14 +#define BEASTLORD 15 +#define BERSERKER 16 #define WARRIORGM 20 #define CLERICGM 21 #define PALADINGM 22 @@ -58,33 +57,92 @@ #define DISCORD_MERCHANT 59 #define ADVENTURERECRUITER 60 #define ADVENTUREMERCHANT 61 -#define LDON_TREASURE 62 //objects you can use /open on first seen in LDONs -#define CORPSE_CLASS 62 //only seen on Danvi's Corpse in Akheva so far.. -#define TRIBUTE_MASTER 63 -#define GUILD_TRIBUTE_MASTER 64 //not sure +#define LDON_TREASURE 62 // objects you can use /open on first seen in LDONs +#define CORPSE_CLASS 62 // only seen on Danvi's Corpse in Akheva so far.. +#define TRIBUTE_MASTER 63 +#define GUILD_TRIBUTE_MASTER 64 // not sure #define NORRATHS_KEEPERS_MERCHANT 67 #define DARK_REIGN_MERCHANT 68 #define FELLOWSHIP_MASTER 69 #define ALT_CURRENCY_MERCHANT 70 #define MERCERNARY_MASTER 71 -#define warrior_1 1 -#define monk_1 64 -#define paladin_1 4 -#define shadow_1 16 -#define bard_1 128 -#define cleric_1 2 -#define necromancer_1 1024 -#define ranger_1 8 -#define druid_1 32 -#define mage_1 4096 -#define wizard_1 2048 -#define enchanter_1 8192 -#define rogue_1 256 -#define shaman_1 512 -#define beastlord_1 16384 -#define berserker_1 32768 -#define call_1 65536 -const char* GetEQClassName(uint8 class_, uint8 level = 0); + +// player class values +#define PLAYER_CLASS_UNKNOWN 0 +#define PLAYER_CLASS_WARRIOR 1 +#define PLAYER_CLASS_CLERIC 2 +#define PLAYER_CLASS_PALADIN 3 +#define PLAYER_CLASS_RANGER 4 +#define PLAYER_CLASS_SHADOWKNIGHT 5 +#define PLAYER_CLASS_DRUID 6 +#define PLAYER_CLASS_MONK 7 +#define PLAYER_CLASS_BARD 8 +#define PLAYER_CLASS_ROGUE 9 +#define PLAYER_CLASS_SHAMAN 10 +#define PLAYER_CLASS_NECROMANCER 11 +#define PLAYER_CLASS_WIZARD 12 +#define PLAYER_CLASS_MAGICIAN 13 +#define PLAYER_CLASS_ENCHANTER 14 +#define PLAYER_CLASS_BEASTLORD 15 +#define PLAYER_CLASS_BERSERKER 16 + +#define PLAYER_CLASS_COUNT 16 + + +// player class bits +#define PLAYER_CLASS_UNKNOWN_BIT 0 +#define PLAYER_CLASS_WARRIOR_BIT 1 +#define PLAYER_CLASS_CLERIC_BIT 2 +#define PLAYER_CLASS_PALADIN_BIT 4 +#define PLAYER_CLASS_RANGER_BIT 8 +#define PLAYER_CLASS_SHADOWKNIGHT_BIT 16 +#define PLAYER_CLASS_DRUID_BIT 32 +#define PLAYER_CLASS_MONK_BIT 64 +#define PLAYER_CLASS_BARD_BIT 128 +#define PLAYER_CLASS_ROGUE_BIT 256 +#define PLAYER_CLASS_SHAMAN_BIT 512 +#define PLAYER_CLASS_NECROMANCER_BIT 1024 +#define PLAYER_CLASS_WIZARD_BIT 2048 +#define PLAYER_CLASS_MAGICIAN_BIT 4096 +#define PLAYER_CLASS_ENCHANTER_BIT 8192 +#define PLAYER_CLASS_BEASTLORD_BIT 16384 +#define PLAYER_CLASS_BERSERKER_BIT 32768 + +#define PLAYER_CLASS_ALL_MASK 65535 // was 65536 + + +#define ARMOR_TYPE_UNKNOWN 0 +#define ARMOR_TYPE_CLOTH 1 +#define ARMOR_TYPE_LEATHER 2 +#define ARMOR_TYPE_CHAIN 3 +#define ARMOR_TYPE_PLATE 4 + +#define ARMOR_TYPE_FIRST ARMOR_TYPE_UNKNOWN +#define ARMOR_TYPE_LAST ARMOR_TYPE_PLATE +#define ARMOR_TYPE_COUNT 5 + + +const char* GetClassIDName(uint8 class_id, uint8 level = 0); +const char* GetPlayerClassName(uint32 player_class_value, uint8 level = 0); + +uint32 GetPlayerClassValue(uint8 class_id); +uint32 GetPlayerClassBit(uint8 class_id); + +uint8 GetClassIDFromPlayerClassValue(uint32 player_class_value); +uint8 GetClassIDFromPlayerClassBit(uint32 player_class_bit); + +bool IsFighterClass(uint8 class_id); +bool IsSpellFighterClass(uint8 class_id); +bool IsNonSpellFighterClass(uint8 class_id); +bool IsCasterClass(uint8 class_id); +bool IsINTCasterClass(uint8 class_id); +bool IsWISCasterClass(uint8 class_id); + +bool IsPlateClass(uint8 class_id); +bool IsChainClass(uint8 class_id); +bool IsLeatherClass(uint8 class_id); +bool IsClothClass(uint8 class_id); +uint8 ClassArmorType(uint8 class_id); + #endif - diff --git a/common/database.h b/common/database.h index 02110b9a6..68197811f 100644 --- a/common/database.h +++ b/common/database.h @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2003 EQEMu Development Team (http://eqemulator.net) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -88,7 +88,7 @@ class Database : public DBcore { public: Database(); Database(const char* host, const char* user, const char* passwd, const char* database,uint32 port); - bool Connect(const char* host, const char* user, const char* passwd, const char* database,uint32 port); + bool Connect(const char* host, const char* user, const char* passwd, const char* database, uint32 port); ~Database(); /* Character Creation */ diff --git a/common/eq_constants.h b/common/eq_constants.h index 728013eba..222d5802f 100644 --- a/common/eq_constants.h +++ b/common/eq_constants.h @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2003 EQEMu Development Team (http://eqemulator.net) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -390,30 +390,47 @@ enum { ET_Scroll = 7 }; -//SpawnAppearance types: -#define AT_Die 0 // this causes the client to keel over and zone to bind point -#define AT_WhoLevel 1 // the level that shows up on /who -#define AT_Invis 3 // 0 = visible, 1 = invisible -#define AT_PVP 4 // 0 = blue, 1 = pvp (red) -#define AT_Light 5 // light type emitted by player (lightstone, shiny shield) -#define AT_Anim 14 // 100=standing, 110=sitting, 111=ducking, 115=feigned, 105=looting -#define AT_Sneak 15 // 0 = normal, 1 = sneaking -#define AT_SpawnID 16 // server to client, sets player spawn id -#define AT_HP 17 // Client->Server, my HP has changed (like regen tic) -#define AT_Linkdead 18 // 0 = normal, 1 = linkdead -#define AT_Levitate 19 // 0=off, 1=flymode, 2=levitate -#define AT_GM 20 // 0 = normal, 1 = GM - all odd numbers seem to make it GM -#define AT_Anon 21 // 0 = normal, 1 = anon, 2 = roleplay -#define AT_GuildID 22 -#define AT_GuildRank 23 // 0=member, 1=officer, 2=leader -#define AT_AFK 24 // 0 = normal, 1 = afk -#define AT_Pet 25 // Param is EntityID of owner, or 0 for when charm breaks -#define AT_Split 28 // 0 = normal, 1 = autosplit on -#define AT_Size 29 // spawn's size -#define AT_NPCName 31 // change PC's name's color to NPC color 0 = normal, 1 = npc name -#define AT_ShowHelm 43 // 0 = do not show helmet graphic, 1 = show graphic -#define AT_DamageState 44 // The damage state of a destructible object (0 through 4) -//#define AT_Trader 300 // Bazzar Trader Mode +//SpawnAppearance types: (compared two clients for server-originating types: SoF & RoF2) +#define AT_Die 0 // this causes the client to keel over and zone to bind point (default action) +#define AT_WhoLevel 1 // the level that shows up on /who +//#define AT_2 2 // unknown +#define AT_Invis 3 // 0 = visible, 1 = invisible +#define AT_PVP 4 // 0 = blue, 1 = pvp (red) +#define AT_Light 5 // light type emitted by player (lightstone, shiny shield) +#define AT_Anim 14 // 100=standing, 110=sitting, 111=ducking, 115=feigned, 105=looting +#define AT_Sneak 15 // 0 = normal, 1 = sneaking +#define AT_SpawnID 16 // server to client, sets player spawn id +#define AT_HP 17 // Client->Server, my HP has changed (like regen tic) +#define AT_Linkdead 18 // 0 = normal, 1 = linkdead +#define AT_Levitate 19 // 0=off, 1=flymode, 2=levitate +#define AT_GM 20 // 0 = normal, 1 = GM - all odd numbers seem to make it GM +#define AT_Anon 21 // 0 = normal, 1 = anon, 2 = roleplay +#define AT_GuildID 22 +#define AT_GuildRank 23 // 0=member, 1=officer, 2=leader +#define AT_AFK 24 // 0 = normal, 1 = afk +#define AT_Pet 25 // Param is EntityID of owner, or 0 for when charm breaks +//#define AT_27 27 // unknown +#define AT_Split 28 // 0 = normal, 1 = autosplit on (not showing in SoF+) (client-to-server only) +#define AT_Size 29 // spawn's size (present: SoF, absent: RoF2) +//#define AT_30 30 // unknown +#define AT_NPCName 31 // change PC's name's color to NPC color 0 = normal, 1 = npc name +//#define AT_32 32 // unknown +//#define AT_33 33 // unknown +//#define AT_34 34 // unknown (present: SoF, absent: RoF2) +//#define AT_35 35 // unknown +//#define AT_36 36 // unknown +//#define AT_37 37 // unknown +//#define AT_38 38 // unknown +//#define AT_39 39 // unknown +#define AT_ShowHelm 43 // 0 = hide graphic, 1 = show graphic +#define AT_DamageState 44 // The damage state of a destructible object (0 through 4) +//#define AT_46 46 // unknown +//#define AT_48 48 // unknown +//#define AT_49 49 // unknown +//#define AT_52 52 // (absent: SoF, present: RoF2) (not a replacement for RoF absent 29 or 34) +//#define AT_53 53 // (absent: SoF, present: RoF2) (not a replacement for RoF absent 29 or 34) + +//#define AT_Trader 300 // Bazaar Trader Mode (not present in SoF or RoF2) // animations for AT_Anim #define ANIM_FREEZE 102 @@ -519,15 +536,40 @@ typedef enum { #define MT_StrikeThrough 339 #define MT_Stun 340 +// TODO: Really should combine above and below into one + //from showeq enum ChatColor { + /* CC_Default = 0, CC_DarkGrey = 1, CC_DarkGreen = 2, CC_DarkBlue = 3, CC_Purple = 5, CC_LightGrey = 6, + */ + + CC_WhiteSmoke = 0, // FF|F0F0F0 + CC_Green = 2, // FF|008000 + CC_BrightBlue = 3, // FF|0040FF + CC_Magenta = 5, // FF|F000F0 + CC_Gray = 6, // FF|808080 + CC_LightGray = 7, // FF|E0E0E0 + //CC_WhiteSmoke2 = 10, // FF|F0F0F0 + CC_DarkGray = 12, // FF|A0A0A0 + CC_Red = 13, // FF|F00000 + CC_Lime = 14, // FF|00F000 + CC_Yellow = 15, // FF|F0F000 + CC_Blue = 16, // FF|0000F0 + CC_LightNavy = 17, // FF|0000AF + CC_Cyan = 18, // FF|00F0F0 + CC_Black = 20, // FF|000000 + + // any index <= 255 that is not defined above + CC_DimGray = 1, // FF|606060 + CC_Default = 1, + CC_User_Say = 256, CC_User_Tell = 257, CC_User_Group = 258, diff --git a/common/item.cpp b/common/item.cpp index 5d1142d2c..2bbc4d306 100644 --- a/common/item.cpp +++ b/common/item.cpp @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2003 EQEMu Development Team (http://eqemulator.net) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -3063,7 +3063,7 @@ bool Item_Struct::IsEquipable(uint16 Race, uint16 Class_) const uint32 Classes_ = Classes; uint32 Races_ = Races; - uint32 Race_ = GetArrayRace(Race); + uint32 Race_ = GetPlayerRaceValue(Race); for (int CurrentClass = 1; CurrentClass <= PLAYER_CLASS_COUNT; ++CurrentClass) { if (Classes_ & 1) { diff --git a/common/patches/ss_define.h b/common/patches/ss_define.h index 8502d02dd..3b2afb5b1 100644 --- a/common/patches/ss_define.h +++ b/common/patches/ss_define.h @@ -1,3 +1,20 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ #define ENCODE(x) void Strategy::Encode_##x(EQApplicationPacket **p, std::shared_ptr dest, bool ack_req) #define DECODE(x) void Strategy::Decode_##x(EQApplicationPacket *__packet) @@ -116,6 +133,12 @@ //a shorter assignment for direct mode #undef IN #define IN(x) emu->x = eq->x; +#define IN_str(x) \ + strncpy(emu->x, eq->x, sizeof(emu->x)); \ + emu->x[sizeof(emu->x)-1] = '\0'; +#define IN_array(x, n) \ + for(__i = 0; __i < n; __i++) \ + emu->x[__i] = eq->x[__i]; //call before any premature returns in an encoder using SETUP_DIRECT_DECODE #define FAIL_DIRECT_DECODE() \ diff --git a/common/races.cpp b/common/races.cpp index 6322b50af..7fd88911b 100644 --- a/common/races.cpp +++ b/common/races.cpp @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemu.org) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,95 +18,831 @@ #include "../common/races.h" -const char* GetRaceName(uint16 race) { - switch(race) { - case HUMAN: - return "Human"; - case BARBARIAN: - return "Barbarian"; - case ERUDITE: - return "Erudite"; - case WOOD_ELF: - return "Wood Elf"; - case HIGH_ELF: - return "High Elf"; - case DARK_ELF: - return "Dark Elf"; - case HALF_ELF: - return "Half Elf"; - case DWARF: - return "Dwarf"; - case TROLL: - return "Troll"; - case OGRE: - return "Ogre"; - case HALFLING: - return "Halfling"; - case GNOME: - return "Gnome"; - case IKSAR: - return "Iksar"; - case WEREWOLF: - return "Werewolf"; - case SKELETON: - return "Skeleton"; - case ELEMENTAL: - return "Elemental"; - case EYE_OF_ZOMM: - return "Eye of Zomm"; - case WOLF_ELEMENTAL: - return "Wolf Elemental"; - case IKSAR_SKELETON: - return "Iksar Skeleton"; - case VAHSHIR: - return "Vah Shir"; - case FROGLOK: - case FROGLOK2: - return "Froglok"; - case DRAKKIN: - return "Drakkin"; - default: - return "Unknown"; +const char* GetRaceIDName(uint16 race_id) +{ + switch (race_id) { + case HUMAN: + return "Human"; + case BARBARIAN: + return "Barbarian"; + case ERUDITE: + return "Erudite"; + case WOOD_ELF: + return "Wood Elf"; + case HIGH_ELF: + return "High Elf"; + case DARK_ELF: + return "Dark Elf"; + case HALF_ELF: + return "Half Elf"; + case DWARF: + return "Dwarf"; + case TROLL: + return "Troll"; + case OGRE: + return "Ogre"; + case HALFLING: + return "Halfling"; + case GNOME: + return "Gnome"; + case IKSAR: + return "Iksar"; + case WEREWOLF: + return "Werewolf"; + case SKELETON: + return "Skeleton"; + case ELEMENTAL: + return "Elemental"; + case EYE_OF_ZOMM: + return "Eye of Zomm"; + case WOLF_ELEMENTAL: + return "Wolf Elemental"; + case IKSAR_SKELETON: + return "Iksar Skeleton"; + case VAHSHIR: + return "Vah Shir"; + case FROGLOK: + case FROGLOK2: + return "Froglok"; + case DRAKKIN: + return "Drakkin"; + default: + return "Unknown"; } } -uint32 GetArrayRace(uint16 race) { - switch(race) { - case HUMAN: - return Array_Race_HUMAN; - case BARBARIAN: - return Array_Race_BARBARIAN; - case ERUDITE: - return Array_Race_ERUDITE; - case WOOD_ELF: - return Array_Race_WOOD_ELF; - case HIGH_ELF: - return Array_Race_HIGH_ELF; - case DARK_ELF: - return Array_Race_DARK_ELF; - case HALF_ELF: - return Array_Race_HALF_ELF; - case DWARF: - return Array_Race_DWARF; - case TROLL: - return Array_Race_TROLL; - case OGRE: - return Array_Race_OGRE; - case HALFLING: - return Array_Race_HALFLING; - case GNOME: - return Array_Race_GNOME; - case IKSAR: - return Array_Race_IKSAR; - case VAHSHIR: - return Array_Race_VAHSHIR; - case FROGLOK: - case FROGLOK2: - return Array_Race_FROGLOK; - case DRAKKIN: - return Array_Race_DRAKKIN; - default: - return Array_Race_UNKNOWN; +const char* GetPlayerRaceName(uint32 player_race_value) +{ + return GetRaceIDName(GetRaceIDFromPlayerRaceValue(player_race_value)); +} + +uint32 GetPlayerRaceValue(uint16 race_id) +{ + switch (race_id) { + case HUMAN: + case BARBARIAN: + case ERUDITE: + case WOOD_ELF: + case HIGH_ELF: + case DARK_ELF: + case HALF_ELF: + case DWARF: + case TROLL: + case OGRE: + case HALFLING: + case GNOME: + return race_id; + case IKSAR: + return PLAYER_RACE_IKSAR; + case VAHSHIR: + return PLAYER_RACE_VAHSHIR; + case FROGLOK: + case FROGLOK2: + return PLAYER_RACE_FROGLOK; + case DRAKKIN: + return PLAYER_RACE_DRAKKIN; + default: + return PLAYER_RACE_UNKNOWN; // watch } } +uint32 GetPlayerRaceBit(uint16 race_id) +{ + switch (race_id) { + case HUMAN: + return PLAYER_RACE_HUMAN_BIT; + case BARBARIAN: + return PLAYER_RACE_BARBARIAN_BIT; + case ERUDITE: + return PLAYER_RACE_ERUDITE_BIT; + case WOOD_ELF: + return PLAYER_RACE_WOOD_ELF_BIT; + case HIGH_ELF: + return PLAYER_RACE_HIGH_ELF_BIT; + case DARK_ELF: + return PLAYER_RACE_DARK_ELF_BIT; + case HALF_ELF: + return PLAYER_RACE_HALF_ELF_BIT; + case DWARF: + return PLAYER_RACE_DWARF_BIT; + case TROLL: + return PLAYER_RACE_TROLL_BIT; + case OGRE: + return PLAYER_RACE_OGRE_BIT; + case HALFLING: + return PLAYER_RACE_HALFLING_BIT; + case GNOME: + return PLAYER_RACE_GNOME_BIT; + case IKSAR: + return PLAYER_RACE_IKSAR_BIT; + case VAHSHIR: + return PLAYER_RACE_VAHSHIR_BIT; + case FROGLOK: + return PLAYER_RACE_FROGLOK_BIT; + case DRAKKIN: + return PLAYER_RACE_DRAKKIN_BIT; + default: + return PLAYER_RACE_UNKNOWN_BIT; + } +} + +uint16 GetRaceIDFromPlayerRaceValue(uint32 player_race_value) +{ + switch (player_race_value) { + case PLAYER_RACE_HUMAN: + case PLAYER_RACE_BARBARIAN: + case PLAYER_RACE_ERUDITE: + case PLAYER_RACE_WOOD_ELF: + case PLAYER_RACE_HIGH_ELF: + case PLAYER_RACE_DARK_ELF: + case PLAYER_RACE_HALF_ELF: + case PLAYER_RACE_DWARF: + case PLAYER_RACE_TROLL: + case PLAYER_RACE_OGRE: + case PLAYER_RACE_HALFLING: + case PLAYER_RACE_GNOME: + return player_race_value; + case PLAYER_RACE_IKSAR: + return IKSAR; + case PLAYER_RACE_VAHSHIR: + return VAHSHIR; + case PLAYER_RACE_FROGLOK: + return FROGLOK; + case PLAYER_RACE_DRAKKIN: + return DRAKKIN; + default: + return PLAYER_RACE_UNKNOWN; // watch + } +} + +uint16 GetRaceIDFromPlayerRaceBit(uint32 player_race_bit) +{ + switch (player_race_bit) { + case PLAYER_RACE_HUMAN_BIT: + return HUMAN; + case PLAYER_RACE_BARBARIAN_BIT: + return BARBARIAN; + case PLAYER_RACE_ERUDITE_BIT: + return ERUDITE; + case PLAYER_RACE_WOOD_ELF_BIT: + return WOOD_ELF; + case PLAYER_RACE_HIGH_ELF_BIT: + return HIGH_ELF; + case PLAYER_RACE_DARK_ELF_BIT: + return DARK_ELF; + case PLAYER_RACE_HALF_ELF_BIT: + return HALF_ELF; + case PLAYER_RACE_DWARF_BIT: + return DWARF; + case PLAYER_RACE_TROLL_BIT: + return TROLL; + case PLAYER_RACE_OGRE_BIT: + return OGRE; + case PLAYER_RACE_HALFLING_BIT: + return HALFLING; + case PLAYER_RACE_GNOME_BIT: + return GNOME; + case PLAYER_RACE_IKSAR_BIT: + return IKSAR; + case PLAYER_RACE_VAHSHIR_BIT: + return VAHSHIR; + case PLAYER_RACE_FROGLOK_BIT: + return FROGLOK; + case PLAYER_RACE_DRAKKIN_BIT: + return DRAKKIN; + default: + return PLAYER_RACE_UNKNOWN; // watch + } +} + + +// PlayerAppearance prep +#define HUMAN_MALE ((HUMAN << 8) | MALE) +#define HUMAN_FEMALE ((HUMAN << 8) | FEMALE) +#define BARBARIAN_MALE ((BARBARIAN << 8) | MALE) +#define BARBARIAN_FEMALE ((BARBARIAN << 8) | FEMALE) +#define ERUDITE_MALE ((ERUDITE << 8) | MALE) +#define ERUDITE_FEMALE ((ERUDITE << 8) | FEMALE) +#define WOOD_ELF_MALE ((WOOD_ELF << 8) | MALE) +#define WOOD_ELF_FEMALE ((WOOD_ELF << 8) | FEMALE) +#define HIGH_ELF_MALE ((HIGH_ELF << 8) | MALE) +#define HIGH_ELF_FEMALE ((HIGH_ELF << 8) | FEMALE) +#define DARK_ELF_MALE ((DARK_ELF << 8) | MALE) +#define DARK_ELF_FEMALE ((DARK_ELF << 8) | FEMALE) +#define HALF_ELF_MALE ((HALF_ELF << 8) | MALE) +#define HALF_ELF_FEMALE ((HALF_ELF << 8) | FEMALE) +#define DWARF_MALE ((DWARF << 8) | MALE) +#define DWARF_FEMALE ((DWARF << 8) | FEMALE) +#define TROLL_MALE ((TROLL << 8) | MALE) +#define TROLL_FEMALE ((TROLL << 8) | FEMALE) +#define OGRE_MALE ((OGRE << 8) | MALE) +#define OGRE_FEMALE ((OGRE << 8) | FEMALE) +#define HALFLING_MALE ((HALFLING << 8) | MALE) +#define HALFLING_FEMALE ((HALFLING << 8) | FEMALE) +#define GNOME_MALE ((GNOME << 8) | MALE) +#define GNOME_FEMALE ((GNOME << 8) | FEMALE) +#define IKSAR_MALE ((IKSAR << 8) | MALE) +#define IKSAR_FEMALE ((IKSAR << 8) | FEMALE) +#define VAHSHIR_MALE ((VAHSHIR << 8) | MALE) +#define VAHSHIR_FEMALE ((VAHSHIR << 8) | FEMALE) +#define FROGLOK_MALE ((FROGLOK << 8) | MALE) +#define FROGLOK_FEMALE ((FROGLOK << 8) | FEMALE) +#define DRAKKIN_MALE ((DRAKKIN << 8) | MALE) +#define DRAKKIN_FEMALE ((DRAKKIN << 8) | FEMALE) + +#define BINDRG(r, g) (((int)r << 8) | g) + + +bool PlayerAppearance::IsValidBeard(uint16 race_id, uint8 gender_id, uint8 beard_value, bool use_luclin) +{ + if (beard_value == 0xFF) + return true; + + if (use_luclin) { + switch (BINDRG(race_id, gender_id)) { + case DWARF_FEMALE: + if (beard_value <= 1) + return true; + break; + case HIGH_ELF_MALE: + case DARK_ELF_MALE: + case HALF_ELF_MALE: + case DRAKKIN_FEMALE: + if (beard_value <= 3) + return true; + break; + case HUMAN_MALE: + case BARBARIAN_MALE: + case ERUDITE_MALE: + case DWARF_MALE: + case HALFLING_MALE: + case GNOME_MALE: + if (beard_value <= 5) + return true; + break; + case DRAKKIN_MALE: + if (beard_value <= 11) + return true; + break; + default: + break; + } + return false; + } + else { + switch (BINDRG(race_id, gender_id)) { + case DRAKKIN_FEMALE: + if (beard_value <= 3) + return true; + break; + case DRAKKIN_MALE: + if (beard_value <= 11) + return true; + break; + default: + break; + } + return false; + } +} + +bool PlayerAppearance::IsValidBeardColor(uint16 race_id, uint8 gender_id, uint8 beard_color_value, bool use_luclin) +{ + if (beard_color_value == 0xFF) + return true; + + switch (BINDRG(race_id, gender_id)) { + case GNOME_MALE: + if (beard_color_value <= 24) + return true; + break; + case HUMAN_MALE: + case BARBARIAN_MALE: + case ERUDITE_MALE: + case HALF_ELF_MALE: + case DWARF_MALE: + case DWARF_FEMALE: + case HALFLING_MALE: + if (beard_color_value <= 19) + return true; + break; + case DARK_ELF_MALE: + if (beard_color_value >= 13 && beard_color_value <= 18) + return true; + break; + case HIGH_ELF_MALE: + if (beard_color_value <= 14) + return true; + break; + case FROGLOK_MALE: + case FROGLOK_FEMALE: + case DRAKKIN_MALE: + case DRAKKIN_FEMALE: + if (beard_color_value <= 3) + return true; + break; + default: + break; + } + return false; +} + +bool PlayerAppearance::IsValidDetail(uint16 race_id, uint8 gender_id, uint32 detail_value, bool use_luclin) +{ + if (detail_value == 0xFFFFFFFF) + return true; + + switch (BINDRG(race_id, gender_id)) { + case DRAKKIN_MALE: + case DRAKKIN_FEMALE: + if (detail_value <= 7) + return true; + break; + default: + break; + } + return false; +} + +bool PlayerAppearance::IsValidEyeColor(uint16 race_id, uint8 gender_id, uint8 eye_color_value, bool use_luclin) +{ + return true; // need valid criteria + + switch (BINDRG(race_id, gender_id)) { + case HUMAN_MALE: + case HUMAN_FEMALE: + case BARBARIAN_MALE: + case BARBARIAN_FEMALE: + case ERUDITE_MALE: + case ERUDITE_FEMALE: + case WOOD_ELF_MALE: + case WOOD_ELF_FEMALE: + case HIGH_ELF_MALE: + case HIGH_ELF_FEMALE: + case DARK_ELF_MALE: + case DARK_ELF_FEMALE: + case HALF_ELF_MALE: + case HALF_ELF_FEMALE: + case DWARF_MALE: + case DWARF_FEMALE: + case OGRE_MALE: + case OGRE_FEMALE: + case HALFLING_MALE: + case HALFLING_FEMALE: + case GNOME_MALE: + case GNOME_FEMALE: + case IKSAR_MALE: + case IKSAR_FEMALE: + case VAHSHIR_MALE: + case VAHSHIR_FEMALE: + if (eye_color_value <= 9) + return true; + break; + case TROLL_MALE: + case TROLL_FEMALE: + if (eye_color_value <= 10) + return true; + break; + case FROGLOK_MALE: + case FROGLOK_FEMALE: + case DRAKKIN_MALE: + case DRAKKIN_FEMALE: + if (eye_color_value <= 11) + return true; + break; + default: + break; + } + return false; +} + +bool PlayerAppearance::IsValidFace(uint16 race_id, uint8 gender_id, uint8 face_value, bool use_luclin) +{ + if (face_value == 0xFF) + return true; + + switch (BINDRG(race_id, gender_id)) { + case DRAKKIN_MALE: + case DRAKKIN_FEMALE: + if (face_value <= 6) + return true; + break; + case HUMAN_MALE: + case HUMAN_FEMALE: + case BARBARIAN_MALE: + case BARBARIAN_FEMALE: + case ERUDITE_MALE: + case ERUDITE_FEMALE: + case WOOD_ELF_MALE: + case WOOD_ELF_FEMALE: + case HIGH_ELF_MALE: + case HIGH_ELF_FEMALE: + case DARK_ELF_MALE: + case DARK_ELF_FEMALE: + case HALF_ELF_MALE: + case HALF_ELF_FEMALE: + case DWARF_MALE: + case DWARF_FEMALE: + case TROLL_MALE: + case TROLL_FEMALE: + case OGRE_MALE: + case OGRE_FEMALE: + case HALFLING_MALE: + case HALFLING_FEMALE: + case GNOME_MALE: + case GNOME_FEMALE: + case IKSAR_MALE: + case IKSAR_FEMALE: + case VAHSHIR_MALE: + case VAHSHIR_FEMALE: + if (face_value <= 7) + return true; + break; + case FROGLOK_MALE: + case FROGLOK_FEMALE: + if (face_value <= 9) + return true; + break; + default: + break; + } + return false; +} + +bool PlayerAppearance::IsValidHair(uint16 race_id, uint8 gender_id, uint8 hair_value, bool use_luclin) +{ + if (hair_value == 0xFF) + return true; + + if (use_luclin) { + switch (BINDRG(race_id, gender_id)) { + case HUMAN_MALE: + case HUMAN_FEMALE: + case BARBARIAN_MALE: + case BARBARIAN_FEMALE: + case WOOD_ELF_MALE: + case WOOD_ELF_FEMALE: + case HIGH_ELF_MALE: + case HIGH_ELF_FEMALE: + case DARK_ELF_MALE: + case DARK_ELF_FEMALE: + case HALF_ELF_MALE: + case HALF_ELF_FEMALE: + case DWARF_MALE: + case DWARF_FEMALE: + case TROLL_FEMALE: + case OGRE_FEMALE: + case HALFLING_MALE: + case HALFLING_FEMALE: + case GNOME_MALE: + case GNOME_FEMALE: + if (hair_value <= 3) + return true; + break; + case ERUDITE_MALE: + if (hair_value <= 5) + return true; + break; + case DRAKKIN_FEMALE: + if (hair_value <= 7) + return true; + break; + case ERUDITE_FEMALE: + case DRAKKIN_MALE: + if (hair_value <= 8) + return true; + break; + default: + break; + } + return false; + } + else { + switch (BINDRG(race_id, gender_id)) { + case DRAKKIN_FEMALE: + if (hair_value <= 7) + return true; + break; + case DRAKKIN_MALE: + if (hair_value <= 8) + return true; + break; + default: + break; + } + return false; + } +} + +bool PlayerAppearance::IsValidHairColor(uint16 race_id, uint8 gender_id, uint8 hair_color_value, bool use_luclin) +{ + if (hair_color_value == 0xFF) + return true; + + switch (BINDRG(race_id, gender_id)) { + case GNOME_MALE: + case GNOME_FEMALE: + if (hair_color_value <= 24) + return true; + break; + case TROLL_FEMALE: + case OGRE_FEMALE: + if (hair_color_value <= 23) + return true; + break; + case HUMAN_MALE: + case HUMAN_FEMALE: + case BARBARIAN_MALE: + case BARBARIAN_FEMALE: + case WOOD_ELF_MALE: + case WOOD_ELF_FEMALE: + case HALF_ELF_MALE: + case HALF_ELF_FEMALE: + case DWARF_MALE: + case DWARF_FEMALE: + case HALFLING_MALE: + case HALFLING_FEMALE: + if (hair_color_value <= 19) + return true; + break; + case DARK_ELF_MALE: + case DARK_ELF_FEMALE: + if (hair_color_value >= 13 && hair_color_value <= 18) + return true; + break; + case HIGH_ELF_MALE: + case HIGH_ELF_FEMALE: + if (hair_color_value <= 14) + return true; + break; + case FROGLOK_MALE: + case FROGLOK_FEMALE: + case DRAKKIN_MALE: + case DRAKKIN_FEMALE: + if (hair_color_value <= 3) + return true; + break; + default: + break; + } + return false; +} + +bool PlayerAppearance::IsValidHead(uint16 race_id, uint8 gender_id, uint8 head_value, bool use_luclin) +{ + if (head_value == 0xFF) + return true; + + if (use_luclin) { + switch (BINDRG(race_id, gender_id)) { + case HUMAN_MALE: + case HUMAN_FEMALE: + case BARBARIAN_MALE: + case BARBARIAN_FEMALE: + case WOOD_ELF_MALE: + case WOOD_ELF_FEMALE: + case HIGH_ELF_MALE: + case HIGH_ELF_FEMALE: + case DARK_ELF_MALE: + case DARK_ELF_FEMALE: + case HALF_ELF_MALE: + case HALF_ELF_FEMALE: + case DWARF_MALE: + case DWARF_FEMALE: + case TROLL_MALE: + case TROLL_FEMALE: + case OGRE_MALE: + case OGRE_FEMALE: + case HALFLING_MALE: + case HALFLING_FEMALE: + case GNOME_MALE: + case GNOME_FEMALE: + case IKSAR_MALE: + case IKSAR_FEMALE: + case VAHSHIR_MALE: + case VAHSHIR_FEMALE: + case FROGLOK_MALE: + case FROGLOK_FEMALE: + case DRAKKIN_MALE: + case DRAKKIN_FEMALE: + if (head_value <= 3) + return true; + break; + case ERUDITE_MALE: + case ERUDITE_FEMALE: + if (head_value <= 4) + return true; + break; + default: + break; + } + return false; + } + else { + switch (BINDRG(race_id, gender_id)) { + case HUMAN_MALE: + case HUMAN_FEMALE: + case BARBARIAN_MALE: + case BARBARIAN_FEMALE: + case ERUDITE_MALE: + case ERUDITE_FEMALE: + case WOOD_ELF_MALE: + case WOOD_ELF_FEMALE: + case HIGH_ELF_MALE: + case HIGH_ELF_FEMALE: + case DARK_ELF_MALE: + case DARK_ELF_FEMALE: + case HALF_ELF_MALE: + case HALF_ELF_FEMALE: + case DWARF_MALE: + case DWARF_FEMALE: + case TROLL_MALE: + case TROLL_FEMALE: + case OGRE_MALE: + case OGRE_FEMALE: + case HALFLING_MALE: + case HALFLING_FEMALE: + case IKSAR_MALE: + case IKSAR_FEMALE: + case VAHSHIR_MALE: + case VAHSHIR_FEMALE: + case FROGLOK_MALE: + case FROGLOK_FEMALE: + case DRAKKIN_MALE: + case DRAKKIN_FEMALE: + if (head_value <= 3) + return true; + break; + case GNOME_MALE: + case GNOME_FEMALE: + if (head_value <= 4) + return true; + break; + default: + break; + } + return false; + } +} + +bool PlayerAppearance::IsValidHeritage(uint16 race_id, uint8 gender_id, uint32 heritage_value, bool use_luclin) +{ + if (heritage_value == 0xFFFFFFFF) + return true; + + switch (BINDRG(race_id, gender_id)) { + case DRAKKIN_MALE: + case DRAKKIN_FEMALE: + if (heritage_value <= 7) // > 5 seems to jumble other features..else, some heritages have 'specialized' features + return true; + break; + default: + break; + } + return false; +} + +bool PlayerAppearance::IsValidTattoo(uint16 race_id, uint8 gender_id, uint32 tattoo_value, bool use_luclin) +{ + if (tattoo_value == 0xFFFFFFFF) + return true; + + switch (BINDRG(race_id, gender_id)) { + case DRAKKIN_MALE: + case DRAKKIN_FEMALE: + if (tattoo_value <= 7) + return true; + break; + default: + break; + } + return false; +} + +bool PlayerAppearance::IsValidTexture(uint16 race_id, uint8 gender_id, uint8 texture_value, bool use_luclin) +{ + if (texture_value == 0xFF) + return true; + + if (use_luclin) { + switch (BINDRG(race_id, gender_id)) { + case HUMAN_MALE: + case HUMAN_FEMALE: + case IKSAR_MALE: + case IKSAR_FEMALE: + case DRAKKIN_MALE: + case DRAKKIN_FEMALE: + if ((texture_value >= 10 && texture_value <= 16) || texture_value <= 4) + return true; + break; + case ERUDITE_MALE: + case ERUDITE_FEMALE: + case HIGH_ELF_MALE: + case HIGH_ELF_FEMALE: + case DARK_ELF_MALE: + case DARK_ELF_FEMALE: + case GNOME_MALE: + case GNOME_FEMALE: + case FROGLOK_MALE: + case FROGLOK_FEMALE: + if ((texture_value >= 10 && texture_value <= 16) || texture_value <= 3) + return true; + break; + case BARBARIAN_MALE: + case BARBARIAN_FEMALE: + case WOOD_ELF_MALE: + case WOOD_ELF_FEMALE: + case HALF_ELF_MALE: + case HALF_ELF_FEMALE: + case DWARF_MALE: + case DWARF_FEMALE: + case TROLL_MALE: + case TROLL_FEMALE: + case OGRE_MALE: + case OGRE_FEMALE: + case HALFLING_MALE: + case HALFLING_FEMALE: + case VAHSHIR_MALE: + case VAHSHIR_FEMALE: + if (texture_value <= 3) + return true; + break; + default: + break; + } + return false; + } + else { + switch (BINDRG(race_id, gender_id)) { + case HUMAN_MALE: + case HUMAN_FEMALE: + case ERUDITE_MALE: + case ERUDITE_FEMALE: + case DRAKKIN_MALE: + case DRAKKIN_FEMALE: + if ((texture_value >= 10 && texture_value <= 16) || texture_value <= 4) + return true; + break; + case HIGH_ELF_MALE: + case HIGH_ELF_FEMALE: + case DARK_ELF_MALE: + case DARK_ELF_FEMALE: + case GNOME_MALE: + case GNOME_FEMALE: + case FROGLOK_MALE: + case FROGLOK_FEMALE: + if ((texture_value >= 10 && texture_value <= 16) || texture_value <= 3) + return true; + break; + case VAHSHIR_MALE: + case VAHSHIR_FEMALE: + if (texture_value == 50 || texture_value <= 3) + return true; + break; + case IKSAR_MALE: + case IKSAR_FEMALE: + if (texture_value == 10 || texture_value <= 4) + return true; + break; + case BARBARIAN_MALE: + case BARBARIAN_FEMALE: + case WOOD_ELF_MALE: + case WOOD_ELF_FEMALE: + case HALF_ELF_MALE: + case HALF_ELF_FEMALE: + case DWARF_MALE: + case DWARF_FEMALE: + case TROLL_MALE: + case TROLL_FEMALE: + case OGRE_MALE: + case OGRE_FEMALE: + case HALFLING_MALE: + case HALFLING_FEMALE: + if (texture_value <= 3) + return true; + break; + default: + break; + } + return false; + } +} + +bool PlayerAppearance::IsValidWoad(uint16 race_id, uint8 gender_id, uint8 woad_value, bool use_luclin) +{ + if (woad_value == 0xFF) + return true; + + if (use_luclin) { + switch (BINDRG(race_id, gender_id)) { + case BARBARIAN_MALE: + case BARBARIAN_FEMALE: + if (woad_value <= 8) + return true; + break; + default: + break; + } + } + return false; +} diff --git a/common/races.h b/common/races.h index 68add5cca..050b2fd78 100644 --- a/common/races.h +++ b/common/races.h @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemu.org) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,92 +15,130 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + #ifndef RACES_H #define RACES_H #include "../common/types.h" -#include + +#define MALE 0 +#define FEMALE 1 +#define NEUTER 2 //theres a big list straight from the client below. -#define HUMAN 1 -#define BARBARIAN 2 -#define ERUDITE 3 -#define WOOD_ELF 4 -#define HIGH_ELF 5 -#define DARK_ELF 6 -#define HALF_ELF 7 -#define DWARF 8 -#define TROLL 9 -#define OGRE 10 -#define HALFLING 11 -#define GNOME 12 -#define WEREWOLF 14 -#define WOLF 42 -#define BEAR 43 -#define SKELETON 60 -#define TIGER 63 -#define ELEMENTAL 75 -#define ALLIGATOR 91 -#define EYE_OF_ZOMM 108 -#define WOLF_ELEMENTAL 120 -#define INVISIBLE_MAN 127 -#define IKSAR 128 -#define VAHSHIR 130 +#define HUMAN 1 +#define BARBARIAN 2 +#define ERUDITE 3 +#define WOOD_ELF 4 +#define HIGH_ELF 5 +#define DARK_ELF 6 +#define HALF_ELF 7 +#define DWARF 8 +#define TROLL 9 +#define OGRE 10 +#define HALFLING 11 +#define GNOME 12 +#define WEREWOLF 14 +#define WOLF 42 +#define BEAR 43 +#define SKELETON 60 +#define TIGER 63 +#define ELEMENTAL 75 +#define ALLIGATOR 91 +#define EYE_OF_ZOMM 108 +#define WOLF_ELEMENTAL 120 +#define INVISIBLE_MAN 127 +#define IKSAR 128 +#define VAHSHIR 130 #define CONTROLLED_BOAT 141 -#define MINOR_ILL_OBJ 142 -#define TREE 143 -#define IKSAR_SKELETON 161 -#define FROGLOK 330 -#define FROGLOK2 74 // Not sure why /who all reports race as 74 for frogloks -#define DRAKKIN 522 // 32768 -#define EMU_RACE_NPC 131069 // was 65533 -#define EMU_RACE_PET 131070 // was 65534 +#define MINOR_ILL_OBJ 142 +#define TREE 143 +#define IKSAR_SKELETON 161 +#define FROGLOK 330 +// TODO: check all clients for (BYTE) usage of '/who all' class and remove FROGLOK2, if possible (330 - 74 = 256 .. WORD->BYTE conversion loss...) +#define FROGLOK2 74 // Not sure why /who all reports race as 74 for frogloks +#define FAIRY 473 +#define DRAKKIN 522 // 32768 +#define EMU_RACE_NPC 131069 // was 65533 +#define EMU_RACE_PET 131070 // was 65534 #define EMU_RACE_UNKNOWN 131071 // was 65535 -#define human_1 1 -#define barbarian_1 2 -#define erudite_1 4 -#define woodelf_1 8 -#define highelf_1 16 -#define darkelf_1 32 -#define halfelf_1 64 -#define dwarf_1 128 -#define troll_1 256 -#define ogre_1 512 -#define halfling_1 1024 -#define gnome_1 2048 -#define iksar_1 4096 -#define vahshir_1 8192 -#define rall_1 16384 //froglok? -#define drakkin_1 32768 +// player race values +#define PLAYER_RACE_UNKNOWN 0 +#define PLAYER_RACE_HUMAN 1 +#define PLAYER_RACE_BARBARIAN 2 +#define PLAYER_RACE_ERUDITE 3 +#define PLAYER_RACE_WOOD_ELF 4 +#define PLAYER_RACE_HIGH_ELF 5 +#define PLAYER_RACE_DARK_ELF 6 +#define PLAYER_RACE_HALF_ELF 7 +#define PLAYER_RACE_DWARF 8 +#define PLAYER_RACE_TROLL 9 +#define PLAYER_RACE_OGRE 10 +#define PLAYER_RACE_HALFLING 11 +#define PLAYER_RACE_GNOME 12 +#define PLAYER_RACE_IKSAR 13 +#define PLAYER_RACE_VAHSHIR 14 +#define PLAYER_RACE_FROGLOK 15 +#define PLAYER_RACE_DRAKKIN 16 -const char* GetRaceName(uint16 race); +#define PLAYER_RACE_COUNT 16 -uint32 GetArrayRace(uint16 race); -inline uint32 GetRaceBitmask(uint16 race) { return uint32(pow(2.0f, float(GetArrayRace(race) - 1))); } -#define Array_Race_UNKNOWN 0 -#define Array_Race_HUMAN 1 -#define Array_Race_BARBARIAN 2 -#define Array_Race_ERUDITE 3 -#define Array_Race_WOOD_ELF 4 -#define Array_Race_HIGH_ELF 5 -#define Array_Race_DARK_ELF 6 -#define Array_Race_HALF_ELF 7 -#define Array_Race_DWARF 8 -#define Array_Race_TROLL 9 -#define Array_Race_OGRE 10 -#define Array_Race_HALFLING 11 -#define Array_Race_GNOME 12 -#define Array_Race_IKSAR 13 -#define Array_Race_VAHSHIR 14 -#define Array_Race_FROGLOK 15 -#define Array_Race_DRAKKIN 16 -#define Array_Race_NPC 17 -#define Array_Race_PET 18 -#define Count_Array_Race 19 // used for array defines, must be the max + 1 -#define PLAYER_RACE_COUNT 16 // The count of all player races +#define PLAYER_RACE_EMU_NPC 17 +#define PLAYER_RACE_EMU_PET 18 +#define PLAYER_RACE_EMU_COUNT 19 + + +// player race bits +#define PLAYER_RACE_UNKNOWN_BIT 0 +#define PLAYER_RACE_HUMAN_BIT 1 +#define PLAYER_RACE_BARBARIAN_BIT 2 +#define PLAYER_RACE_ERUDITE_BIT 4 +#define PLAYER_RACE_WOOD_ELF_BIT 8 +#define PLAYER_RACE_HIGH_ELF_BIT 16 +#define PLAYER_RACE_DARK_ELF_BIT 32 +#define PLAYER_RACE_HALF_ELF_BIT 64 +#define PLAYER_RACE_DWARF_BIT 128 +#define PLAYER_RACE_TROLL_BIT 256 +#define PLAYER_RACE_OGRE_BIT 512 +#define PLAYER_RACE_HALFLING_BIT 1024 +#define PLAYER_RACE_GNOME_BIT 2048 +#define PLAYER_RACE_IKSAR_BIT 4096 +#define PLAYER_RACE_VAHSHIR_BIT 8192 +#define PLAYER_RACE_FROGLOK_BIT 16384 +#define PLAYER_RACE_DRAKKIN_BIT 32768 + +#define PLAYER_RACE_ALL_MASK 65535 + + +const char* GetRaceIDName(uint16 race_id); +const char* GetPlayerRaceName(uint32 player_race_value); + +uint32 GetPlayerRaceValue(uint16 race_id); +uint32 GetPlayerRaceBit(uint16 race_id); + +uint16 GetRaceIDFromPlayerRaceValue(uint32 player_race_value); +uint16 GetRaceIDFromPlayerRaceBit(uint32 player_race_bit); + + +// player race-/gender-based model feature validators +namespace PlayerAppearance +{ + bool IsValidBeard(uint16 race_id, uint8 gender_id, uint8 beard_value, bool use_luclin = true); + bool IsValidBeardColor(uint16 race_id, uint8 gender_id, uint8 beard_color_value, bool use_luclin = true); + bool IsValidDetail(uint16 race_id, uint8 gender_id, uint32 detail_value, bool use_luclin = true); + bool IsValidEyeColor(uint16 race_id, uint8 gender_id, uint8 eye_color_value, bool use_luclin = true); + bool IsValidFace(uint16 race_id, uint8 gender_id, uint8 face_value, bool use_luclin = true); + bool IsValidHair(uint16 race_id, uint8 gender_id, uint8 hair_value, bool use_luclin = true); + bool IsValidHairColor(uint16 race_id, uint8 gender_id, uint8 hair_color_value, bool use_luclin = true); + bool IsValidHead(uint16 race_id, uint8 gender_id, uint8 head_value, bool use_luclin = true); + bool IsValidHeritage(uint16 race_id, uint8 gender_id, uint32 heritage_value, bool use_luclin = true); + bool IsValidTattoo(uint16 race_id, uint8 gender_id, uint32 tattoo_value, bool use_luclin = true); + bool IsValidTexture(uint16 race_id, uint8 gender_id, uint8 texture_value, bool use_luclin = true); + bool IsValidWoad(uint16 race_id, uint8 gender_id, uint8 woad_value, bool use_luclin = true); +} /* diff --git a/common/ruletypes.h b/common/ruletypes.h index 641266b2f..6c253722a 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -1,5 +1,20 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ #ifndef RULE_CATEGORY #define RULE_CATEGORY(name) @@ -516,10 +531,14 @@ RULE_CATEGORY_END() #ifdef BOTS RULE_CATEGORY(Bots) RULE_INT(Bots, AAExpansion, 8) // Bots get AAs through this expansion +RULE_INT(Bots, CommandSpellRank, 1) // Filters bot command spells by rank (1, 2 and 3 are valid filters - any other number allows all ranks) RULE_INT(Bots, CreationLimit, 150) // Number of bots that each account can create RULE_BOOL(Bots, FinishBuffing, false) // Allow for buffs to complete even if the bot caster is out of mana. Only affects buffing out of combat. RULE_BOOL(Bots, GroupBuffing, false) // Bots will cast single target buffs as group buffs, default is false for single. Does not make single target buffs work for MGB. +RULE_INT(Bots, HealRotationMaxMembers, 24) // Maximum number of heal rotation members +RULE_INT(Bots, HealRotationMaxTargets, 12) // Maximum number of heal rotation targets RULE_REAL(Bots, ManaRegen, 2.0) // Adjust mana regen for bots, 1 is fast and higher numbers slow it down 3 is about the same as players. +RULE_BOOL(Bots, PreferNoManaCommandSpells, true) // Give sorting priority to newer no-mana spells (i.e., 'Bind Affinity') RULE_BOOL(Bots, QuestableSpawnLimit, false) // Optional quest method to manage bot spawn limits using the quest_globals name bot_spawn_limit, see: /bazaar/Aediles_Thrall.pl RULE_BOOL(Bots, QuestableSpells, false) // Anita Thrall's (Anita_Thrall.pl) Bot Spell Scriber quests. RULE_INT(Bots, SpawnLimit, 71) // Number of bots a character can have spawned at one time, You + 71 bots is a 12 group raid diff --git a/common/shareddb.cpp b/common/shareddb.cpp index 79bac527e..efe7358db 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -1,3 +1,21 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + #include #include @@ -1647,6 +1665,7 @@ void SharedDatabase::LoadSpells(void *data, int max_spells) { sp[tempid].short_buff_box = atoi(row[154]); sp[tempid].descnum = atoi(row[155]); + sp[tempid].typedescnum = atoi(row[156]); sp[tempid].effectdescnum = atoi(row[157]); sp[tempid].npc_no_los = atoi(row[159]) != 0; @@ -2025,22 +2044,3 @@ void SharedDatabase::SaveCharacterInspectMessage(uint32 character_id, const Insp std::string query = StringFormat("REPLACE INTO `character_inspect_messages` (id, inspect_message) VALUES (%u, '%s')", character_id, EscapeString(message->text).c_str()); auto results = QueryDatabase(query); } - -#ifdef BOTS -void SharedDatabase::GetBotInspectMessage(uint32 bot_id, InspectMessage_Struct* message) -{ - std::string query = StringFormat("SELECT `inspect_message` FROM `bot_inspect_messages` WHERE `bot_id` = %i LIMIT 1", bot_id); - auto results = QueryDatabase(query); - auto row = results.begin(); - memset(message, '\0', sizeof(InspectMessage_Struct)); - for (auto row = results.begin(); row != results.end(); ++row) { - memcpy(message, row[0], sizeof(InspectMessage_Struct)); - } -} - -void SharedDatabase::SetBotInspectMessage(uint32 bot_id, const InspectMessage_Struct* message) -{ - std::string query = StringFormat("REPLACE INTO `bot_inspect_messages` (bot_id, inspect_message) VALUES (%u, '%s')", bot_id, EscapeString(message->text).c_str()); - auto results = QueryDatabase(query); -} -#endif diff --git a/common/shareddb.h b/common/shareddb.h index ebb2ae961..45db3e3c0 100644 --- a/common/shareddb.h +++ b/common/shareddb.h @@ -1,3 +1,21 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + #ifndef SHAREDDB_H_ #define SHAREDDB_H_ @@ -126,11 +144,6 @@ class SharedDatabase : public Database void LoadBaseData(void *data, int max_level); const BaseDataStruct* GetBaseData(int lvl, int cl); -#ifdef BOTS - void GetBotInspectMessage(uint32 botid, InspectMessage_Struct* message); - void SetBotInspectMessage(uint32 botid, const InspectMessage_Struct* message); -#endif - protected: std::unique_ptr skill_caps_mmf; diff --git a/common/spdat.h b/common/spdat.h index 555ea67ef..0e9096f1a 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2005 EQEMu Development Team (http://eqemulator.net) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -639,7 +639,7 @@ typedef enum { // number. note that the id field is counted as 0, this way the numbers // here match the numbers given to sep in the loading function net.cpp // -#define SPELL_LOAD_FIELD_COUNT 231 +#define SPELL_LOAD_FIELD_COUNT 236 struct SPDat_Spell_Struct { @@ -707,7 +707,7 @@ struct SPDat_Spell_Struct // 152 & 153: all set to 0 /* 154 */ int8 short_buff_box; // != 0, goes to short buff box. /* 155 */ int descnum; // eqstr of description of spell -/* 156 */ //int typedescnum; // eqstr of type description +/* 156 */ int typedescnum; // eqstr of type description /* 157 */ int effectdescnum; // eqstr of effect description /* 158 */ //Category Desc ID 3 /* 159 */ bool npc_no_los; diff --git a/common/version.h b/common/version.h index 04bf9189c..64c063917 100644 --- a/common/version.h +++ b/common/version.h @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2013 EQEMu Development Team (http://eqemulator.net) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -32,7 +32,7 @@ #define CURRENT_BINARY_DATABASE_VERSION 9096 #ifdef BOTS - #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9000 + #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9002 #else #define CURRENT_BINARY_BOTS_DATABASE_VERSION 0 // must be 0 #endif diff --git a/utils/scripts/opcode_handlers.py b/utils/scripts/opcode_handlers.py index d428046ba..fb3923209 100644 --- a/utils/scripts/opcode_handlers.py +++ b/utils/scripts/opcode_handlers.py @@ -745,6 +745,7 @@ def discoverserverhandlers(): locations['Zone'].append('/zone/aa.cpp') locations['Zone'].append('/zone/attack.cpp') locations['Zone'].append('/zone/bot.cpp') + locations['Zone'].append('/zone/bot_command.cpp') locations['Zone'].append('/zone/client.cpp') locations['Zone'].append('/zone/client_packet.cpp') locations['Zone'].append('/zone/client_process.cpp') diff --git a/utils/sql/git/bots/bots_db_update_manifest.txt b/utils/sql/git/bots/bots_db_update_manifest.txt index 1612046dd..12e5996f2 100644 --- a/utils/sql/git/bots/bots_db_update_manifest.txt +++ b/utils/sql/git/bots/bots_db_update_manifest.txt @@ -1,4 +1,6 @@ 9000|2015_09_30_bots.sql|SHOW TABLES LIKE 'bot_data'|empty| +9001|2016_03_24_bots_command_settings.sql|SHOW TABLES LIKE 'bot_command_settings'|empty| +9002|2016_03_24_bots_command_rules.sql|SELECT * FROM `rule_values` WHERE `rule_name` LIKE 'Bots:CommandSpellRank'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/bots/required/2016_03_24_bots_command_rules.sql b/utils/sql/git/bots/required/2016_03_24_bots_command_rules.sql new file mode 100644 index 000000000..be3009944 --- /dev/null +++ b/utils/sql/git/bots/required/2016_03_24_bots_command_rules.sql @@ -0,0 +1,5 @@ +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES +(1, 'Bots:CommandSpellRank', '1', 'Filters bot command spells by rank (1, 2 and 3 are valid filters - any other number allows all ranks)'), +(1, 'Bots:HealRotationMaxMembers', '24', 'Maximum number of heal rotation members'), +(1, 'Bots:HealRotationMaxTargets', '12', 'Maximum number of heal rotation targets'), +(1, 'Bots:PreferNoManaCommandSpells', 'true', 'Give sorting priority to newer no-mana spells (i.e., \'Bind Affinity\')'); diff --git a/utils/sql/git/bots/required/2016_03_24_bots_command_settings.sql b/utils/sql/git/bots/required/2016_03_24_bots_command_settings.sql new file mode 100644 index 000000000..7b37e466f --- /dev/null +++ b/utils/sql/git/bots/required/2016_03_24_bots_command_settings.sql @@ -0,0 +1,102 @@ +CREATE TABLE `bot_command_settings` ( + `bot_command` varchar(128) NOT NULL DEFAULT '', + `access` int(11) NOT NULL DEFAULT '0', + `aliases` varchar(256) NOT NULL DEFAULT '', + PRIMARY KEY (`bot_command`), + UNIQUE KEY `UK_bot_command_settings_1` (`bot_command`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +INSERT INTO `bot_command_settings` VALUES +('actionable', 0, ''), +('aggressive', 0, 'agg'), +('attack', 0, 'atk'), +('bindaffinity', 0, 'bind'), +('bot', 0, 'b'), +('botappearance', 0, 'app|appearance'), +('botbeardcolor', 0, 'bc|beardcolor'), +('botbeardstyle', 0, 'bs|beardstyle'), +('botcamp', 0, 'camp'), +('botclone', 200, 'clone'), +('botcreate', 0, 'create'), +('botdelete', 0, 'delete'), +('botdetails', 0, 'details'), +('botdyearmor', 0, 'dyearmor'), +('boteyes', 0, 'eyes'), +('botface', 0, 'face'), +('botfollowdistance', 0, 'followd'), +('botgroup', 0, 'bg'), +('botgroupaddmember', 0, 'bgadd'), +('botgroupcreate', 0, 'bgcreate'), +('botgroupdelete', 0, 'bgdelete'), +('botgrouplist', 0, 'bglist'), +('botgroupload', 0, 'bgload'), +('botgroupremovemember', 0, 'bgremove'), +('bothaircolor', 0, 'hc|haircolor'), +('bothairstyle', 0, 'hs|hairstyle'), +('botheritage', 0, 'her|heritage'), +('botinspectmessage', 0, 'inspect'), +('botlist', 0, 'list'), +('botoutofcombat', 0, 'ooc|outofcombat'), +('botreport', 0, 'report|health|mana'), +('botspawn', 0, 'spawn'), +('botstance', 0, 'stance'), +('botsummon', 0, 'summon'), +('bottattoo', 0, 'tattoo'), +('bottogglearcher', 0, 'archer|togglearcher'), +('bottogglehelm', 0, 'helm|togglehelm'), +('botupdate', 0, 'update'), +('botwoad', 0, 'woad'), +('charm', 0, ''), +('circle', 0, 'cir'), +('cure', 0, ''), +('defensive', 0, 'def'), +('depart', 0, 'dep'), +('escape', 0, 'evac|succor'), +('findaliases', 0, 'alias'), +('follow', 0, ''), +('guard', 0, ''), +('healrotation', 0, 'hr'), +('healrotationadaptivetargeting', 0, 'hradapt'), +('healrotationaddmember', 0, 'hraddm'), +('healrotationaddtarget', 0, 'hraddt'), +('healrotationadjustcritical', 0, 'hrcrit'), +('healrotationadjustsafe', 0, 'hrsafe'), +('healrotationcastingoverride', 0, 'hroverride'), +('healrotationchangeinterval', 0, 'hrinterval'), +('healrotationcleartargets', 0, 'hrclear'), +('healrotationcreate', 0, 'hrcreate'), +('healrotationfastheals', 0, 'hrfastheals'), +('healrotationlist', 0, 'hrlist'), +('healrotationremovemember', 0, 'hrremm'), +('healrotationremovetarget', 0, 'hrremt'), +('healrotationresetlimits', 0, 'hrreset'), +('healrotationstart', 0, 'hrstart'), +('healrotationstop', 0, 'hrstop'), +('help', 0, '?'), +('hold', 0, ''), +('identify', 0, 'lore'), +('inventory', 0, 'inv'), +('inventorygive', 0, 'invgive'), +('inventorylist', 0, 'invlist'), +('inventoryremove', 0, 'invremove'), +('invisibility', 0, 'invis'), +('levitation', 0, 'lev'), +('lull', 0, 'calm|pacify'), +('mesmerize', 0, 'mez'), +('movementspeed', 0, 'sow'), +('pet', 0, 'p'), +('petremove', 0, 'prem'), +('petsettype', 0, 'pset'), +('picklock', 0, 'pl'), +('portal', 0, 'port'), +('pull', 0, ''), +('release', 0, ''), +('resistance', 0, 'resist'), +('resurrect', 0, 'revive'), +('rune', 0, ''), +('sendhome', 0, 'gate'), +('size', 0, ''), +('summoncorpse', 0, 'scorpse'), +('taunt', 0, ''), +('track', 0, ''), +('waterbreathing', 0, 'wb|eb'); diff --git a/world/client.cpp b/world/client.cpp index dc17d7b4d..d0ecac3c2 100644 --- a/world/client.cpp +++ b/world/client.cpp @@ -1,3 +1,21 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + #include "../common/global_define.h" #include "../common/eq_packet.h" #include "../common/eq_stream_intf.h" @@ -498,7 +516,7 @@ bool Client::HandleNameApprovalPacket(const EQApplicationPacket *app) uchar race = app->pBuffer[64]; uchar clas = app->pBuffer[68]; - Log.Out(Logs::Detail, Logs::World_Server, "Name approval request. Name=%s, race=%s, class=%s", char_name, GetRaceName(race), GetEQClassName(clas)); + Log.Out(Logs::Detail, Logs::World_Server, "Name approval request. Name=%s, race=%s, class=%s", char_name, GetRaceIDName(race), GetClassIDName(clas)); EQApplicationPacket *outapp; outapp = new EQApplicationPacket; diff --git a/world/clientlist.cpp b/world/clientlist.cpp index b6b22a6d4..84da0fb08 100644 --- a/world/clientlist.cpp +++ b/world/clientlist.cpp @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2005 EQEMu Development Team (http://eqemulator.net) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,6 +15,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + #include "../common/global_define.h" #include "clientlist.h" #include "zoneserver.h" @@ -1064,7 +1065,7 @@ void ClientList::ConsoleSendWhoAll(const char* to, int16 admin, Who_All_Struct* if (cle->Anon() == 2) { // Roleplay if (admin >= 100 && admin >= cle->Admin()) - sprintf(line, " %s[RolePlay %i %s] %s (%s)%s zone: %s%s%s", tmpgm, cle->level(), GetEQClassName(cle->class_(),cle->level()), cle->name(), GetRaceName(cle->race()), tmpguild, tmpZone, LFG, accinfo); + sprintf(line, " %s[RolePlay %i %s] %s (%s)%s zone: %s%s%s", tmpgm, cle->level(), GetClassIDName(cle->class_(),cle->level()), cle->name(), GetRaceIDName(cle->race()), tmpguild, tmpZone, LFG, accinfo); else if (cle->Admin() >= 80 && admin < 80 && cle->GetGM()) { iterator.Advance(); continue; @@ -1074,7 +1075,7 @@ void ClientList::ConsoleSendWhoAll(const char* to, int16 admin, Who_All_Struct* } else if (cle->Anon() == 1) { // Anon if (admin >= 100 && admin >= cle->Admin()) - sprintf(line, " %s[ANON %i %s] %s (%s)%s zone: %s%s%s", tmpgm, cle->level(), GetEQClassName(cle->class_(),cle->level()), cle->name(), GetRaceName(cle->race()), tmpguild, tmpZone, LFG, accinfo); + sprintf(line, " %s[ANON %i %s] %s (%s)%s zone: %s%s%s", tmpgm, cle->level(), GetClassIDName(cle->class_(),cle->level()), cle->name(), GetRaceIDName(cle->race()), tmpguild, tmpZone, LFG, accinfo); else if (cle->Admin() >= 80 && cle->GetGM()) { iterator.Advance(); continue; @@ -1083,7 +1084,7 @@ void ClientList::ConsoleSendWhoAll(const char* to, int16 admin, Who_All_Struct* sprintf(line, " %s[ANONYMOUS] %s%s%s", tmpgm, cle->name(), LFG, accinfo); } else - sprintf(line, " %s[%i %s] %s (%s)%s zone: %s%s%s", tmpgm, cle->level(), GetEQClassName(cle->class_(),cle->level()), cle->name(), GetRaceName(cle->race()), tmpguild, tmpZone, LFG, accinfo); + sprintf(line, " %s[%i %s] %s (%s)%s zone: %s%s%s", tmpgm, cle->level(), GetClassIDName(cle->class_(),cle->level()), cle->name(), GetRaceIDName(cle->race()), tmpguild, tmpZone, LFG, accinfo); AppendAnyLenString(&output, &outsize, &outlen, line); if (outlen >= 3584) { diff --git a/world/eqw.cpp b/world/eqw.cpp index 04d5d4c8b..2bed01919 100644 --- a/world/eqw.cpp +++ b/world/eqw.cpp @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2006 EQEMu Development Team (http://eqemulator.net) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -193,9 +193,9 @@ std::map EQW::GetPlayerDetails(Const_char *char_name) { res["location_id"] = itoa(cle->zone()); res["ip"] = long2ip(cle->GetIP()); res["level"] = itoa(cle->level()); - res["race"] = GetRaceName(cle->race()); + res["race"] = GetRaceIDName(cle->race()); res["race_id"] = itoa(cle->race()); - res["class"] = GetEQClassName(cle->class_()); + res["class"] = GetClassIDName(cle->class_()); res["class_id"] = itoa(cle->class_()); res["guild_id"] = itoa(cle->GuildID()); res["guild"] = guild_mgr.GetGuildName(cle->GuildID()); diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index ba9aa8620..532cc86cd 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -8,6 +8,8 @@ SET(zone_sources beacon.cpp bonuses.cpp bot.cpp + bot_command.cpp + bot_database.cpp botspellsai.cpp client.cpp client_mods.cpp @@ -30,6 +32,7 @@ SET(zone_sources guild.cpp guild_mgr.cpp hate_list.cpp + heal_rotation.cpp horse.cpp inventory.cpp loottables.cpp @@ -131,6 +134,8 @@ SET(zone_headers basic_functions.h beacon.h bot.h + bot_command.h + bot_database.h bot_structs.h client.h client_packet.h @@ -149,6 +154,7 @@ SET(zone_headers groups.h guild_mgr.h hate_list.h + heal_rotation.h horse.h lua_bit.h lua_client.h diff --git a/zone/aa.cpp b/zone/aa.cpp index 7e00da07c..403f9755d 100644 --- a/zone/aa.cpp +++ b/zone/aa.cpp @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator -Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net) +Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1428,7 +1428,7 @@ bool Mob::CanUseAlternateAdvancementRank(AA::Rank *rank) { } } - auto race = GetArrayRace(GetBaseRace()); + auto race = GetPlayerRaceValue(GetBaseRace()); race = race > 16 ? 1 : race; if(!(ability->races & (1 << (race - 1)))) { return false; diff --git a/zone/bot.cpp b/zone/bot.cpp index dee437849..447f38834 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -1,3 +1,21 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + #ifdef BOTS #include "bot.h" @@ -53,17 +71,10 @@ Bot::Bot(NPCType npcTypeData, Client* botOwner) : NPC(&npcTypeData, nullptr, glm SetHasBeenSummoned(false); SetTaunting(GetClass() == WARRIOR); SetDefaultBotStance(); - SetInHealRotation(false); - SetHealRotationActive(false); - SetHasHealedThisCycle(false); - ClearHealRotationLeader(); - ClearHealRotationTargets(); - ClearHealRotationMembers(); - SetHealRotationNextHealTime(0); - SetHealRotationTimer(0); - SetNumHealRotationMembers(0); - SetBardUseOutOfCombatSongs(GetClass() == BARD); + + SetAltOutOfCombatBehavior(GetClass() == BARD); // will need to be updated if more classes make use of this flag SetShowHelm(true); + SetPauseAI(false); CalcChanceToCast(); rest_timer.Disable(); SetFollowDistance(BOT_DEFAULT_FOLLOW_DISTANCE); @@ -81,9 +92,6 @@ Bot::Bot(NPCType npcTypeData, Client* botOwner) : NPC(&npcTypeData, nullptr, glm for (int i = 0; i < MaxTimer; i++) timers[i] = 0; - for(int i = 0; i < MaxHealRotationTargets; i++) - _healRotationTargets[i] = 0; - strcpy(this->name, this->GetCleanName()); memset(&m_Light, 0, sizeof(LightProfile_Struct)); } @@ -132,21 +140,13 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to SetHasBeenSummoned(false); LoadStance(); SetTaunting((GetClass() == WARRIOR || GetClass() == PALADIN || GetClass() == SHADOWKNIGHT) && (GetBotStance() == BotStanceAggressive)); - SetInHealRotation(false); - SetHealRotationActive(false); - SetHasHealedThisCycle(false); - SetHealRotationUseFastHeals(false); - ClearHealRotationLeader(); - ClearHealRotationTargets(); - ClearHealRotationMembers(); - SetHealRotationNextHealTime(0); - SetHealRotationTimer(0); - SetNumHealRotationMembers(0); + SetPauseAI(false); + CalcChanceToCast(); rest_timer.Disable(); SetFollowDistance(BOT_DEFAULT_FOLLOW_DISTANCE); strcpy(this->name, this->GetCleanName()); - database.GetBotInspectMessage(this->GetBotID(), &_botInspectMessage); + botdb.GetInspectMessage(this->GetBotID(), &_botInspectMessage); LoadGuildMembership(&_guildId, &_guildRank, &_guildName); std::string TempErrorMessage; EquipBot(&TempErrorMessage); @@ -158,9 +158,6 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to for (int i = 0; i < MaxTimer; i++) timers[i] = 0; - for(int i = 0; i < MaxHealRotationTargets; i++) - _healRotationTargets[i] = 0; - GenerateBaseStats(); LoadTimers(); LoadAAs(); @@ -187,6 +184,8 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to Bot::~Bot() { AI_Stop(); + LeaveHealRotationMemberPool(); + if(HasGroup()) Bot::RemoveBotFromGroup(this, GetGroup()); @@ -226,18 +225,18 @@ void Bot::ChangeBotArcherWeapons(bool isArcher) { BotAddEquipItem(MainPrimary, GetBotItemBySlot(MainPrimary)); BotAddEquipItem(MainSecondary, GetBotItemBySlot(MainSecondary)); SetAttackTimer(); - BotGroupSay(this, "My blade is ready."); + BotGroupSay(this, "My blade is ready"); } else { BotRemoveEquipItem(MainPrimary); BotRemoveEquipItem(MainSecondary); BotAddEquipItem(MainAmmo, GetBotItemBySlot(MainAmmo)); BotAddEquipItem(MainSecondary, GetBotItemBySlot(MainRange)); SetAttackTimer(); - BotGroupSay(this, "My bow is true and ready."); + BotGroupSay(this, "My bow is true and ready"); } } else - BotGroupSay(this, "I don't know how to use a bow."); + BotGroupSay(this, "I don't know how to use a bow"); } void Bot::Sit() { @@ -1302,236 +1301,236 @@ void Bot::LoadAAs() { } } -bool Bot::IsValidRaceClassCombo() { - bool Result = false; - - switch(GetRace()) { - case 1: // Human - switch(GetClass()) { - case 1: // Warrior - case 2: // Cleric - case 3: // Paladin - case 4: // Ranger - case 5: // Shadowknight - case 6: // Druid - case 7: // Monk - case 8: // Bard - case 9: // Rogue - case 11: // Necromancer - case 12: // Wizard - case 13: // Magician - case 14: // Enchanter - Result = true; - break; - } - break; - case 2: // Barbarian - switch(GetClass()) { - case 1: // Warrior - case 9: // Rogue - case 10: // Shaman - case 15: // Beastlord - case 16: // Berserker - Result = true; - break; - } - break; - case 3: // Erudite - switch(GetClass()) { - case 2: // Cleric - case 3: // Paladin - case 5: // Shadowknight - case 11: // Necromancer - case 12: // Wizard - case 13: // Magician - case 14: // Enchanter - Result = true; - break; - } - break; - case 4: // Wood Elf - switch(GetClass()) { - case 1: // Warrior - case 4: // Ranger - case 6: // Druid - case 8: // Bard - case 9: // Rogue - Result = true; - break; - } - break; - case 5: // High Elf - switch(GetClass()) { - case 2: // Cleric - case 3: // Paladin - case 12: // Wizard - case 13: // Magician - case 14: // Enchanter - Result = true; - break; - } - break; - case 6: // Dark Elf - switch(GetClass()) { - case 1: // Warrior - case 2: // Cleric - case 5: // Shadowknight - case 9: // Rogue - case 11: // Necromancer - case 12: // Wizard - case 13: // Magician - case 14: // Enchanter - Result = true; - break; - } - break; - case 7: // Half Elf - switch(GetClass()) { - case 1: // Warrior - case 3: // Paladin - case 4: // Ranger - case 6: // Druid - case 8: // Bard - case 9: // Rogue - Result = true; - break; - } - break; - case 8: // Dwarf - switch(GetClass()) { - case 1: // Warrior - case 2: // Cleric - case 3: // Paladin - case 9: // Rogue - case 16: // Berserker - Result = true; - break; - } - break; - case 9: // Troll - switch(GetClass()) { - case 1: // Warrior - case 5: // Shadowknight - case 10: // Shaman - case 15: // Beastlord - case 16: // Berserker - Result = true; - break; - } - break; - case 10: // Ogre - switch(GetClass()) { - case 1: // Warrior - case 5: // Shadowknight - case 10: // Shaman - case 15: // Beastlord - case 16: // Berserker - Result = true; - break; - } - break; - case 11: // Halfling - switch(GetClass()) { - case 1: // Warrior - case 2: // Cleric - case 3: // Paladin - case 4: // Ranger - case 6: // Druid - case 9: // Rogue - Result = true; - break; - } - break; - case 12: // Gnome - switch(GetClass()) { - case 1: // Warrior - case 2: // Cleric - case 3: // Paladin - case 5: // Shadowknight - case 9: // Rogue - case 11: // Necromancer - case 12: // Wizard - case 13: // Magician - case 14: // Enchanter - Result = true; - break; - } - break; - case 128: // Iksar - switch(GetClass()) { - case 1: // Warrior - case 5: // Shadowknight - case 7: // Monk - case 10: // Shaman - case 11: // Necromancer - case 15: // Beastlord - Result = true; - break; - } - break; - case 130: // Vah Shir - switch(GetClass()) { - case 1: // Warrior - case 8: // Bard - case 9: // Rogue - case 10: // Shaman - case 15: // Beastlord - case 16: // Berserker - Result = true; - break; - } - break; - case 330: // Froglok - switch(GetClass()) { - case 1: // Warrior - case 2: // Cleric - case 3: // Paladin - case 5: // Shadowknight - case 9: // Rogue - case 10: // Shaman - case 11: // Necromancer - case 12: // Wizard - Result = true; - break; - } - break; - case 522: // Drakkin - switch(GetClass()) { - case 1: // Warrior - case 2: // Cleric - case 3: // Paladin - case 4: // Ranger - case 5: // Shadowknight - case 6: // Druid - case 7: // Monk - case 8: // Bard - case 9: // Rogue - case 11: // Necromancer - case 12: // Wizard - case 13: // Magician - case 14: // Enchanter - Result = true; - break; - } - break; - } - - return Result; +bool Bot::IsValidRaceClassCombo() +{ + return Bot::IsValidRaceClassCombo(GetRace(), GetClass()); } -bool Bot::IsValidName() { - bool Result = false; - std::string TempBotName = std::string(this->GetCleanName()); - - for(int iCounter = 0; iCounter < TempBotName.length(); iCounter++) { - if(isalpha(TempBotName[iCounter]) || TempBotName[iCounter] == '_') - Result = true; +bool Bot::IsValidRaceClassCombo(uint16 r, uint8 c) +{ + switch (r) { + case HUMAN: + switch (c) { + case WARRIOR: + case CLERIC: + case PALADIN: + case RANGER: + case SHADOWKNIGHT: + case DRUID: + case MONK: + case BARD: + case ROGUE: + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + return true; + } + break; + case BARBARIAN: + switch (c) { + case WARRIOR: + case ROGUE: + case SHAMAN: + case BEASTLORD: + case BERSERKER: + return true; + } + break; + case ERUDITE: + switch (c) { + case CLERIC: + case PALADIN: + case SHADOWKNIGHT: + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + return true; + } + break; + case WOOD_ELF: + switch (c) { + case WARRIOR: + case RANGER: + case DRUID: + case BARD: + case ROGUE: + return true; + } + break; + case HIGH_ELF: + switch (c) { + case CLERIC: + case PALADIN: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + return true; + } + break; + case DARK_ELF: + switch (c) { + case WARRIOR: + case CLERIC: + case SHADOWKNIGHT: + case ROGUE: + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + return true; + } + break; + case HALF_ELF: + switch (c) { + case WARRIOR: + case PALADIN: + case RANGER: + case DRUID: + case BARD: + case ROGUE: + return true; + } + break; + case DWARF: + switch (c) { + case WARRIOR: + case CLERIC: + case PALADIN: + case ROGUE: + case BERSERKER: + return true; + } + break; + case TROLL: + switch (c) { + case WARRIOR: + case SHADOWKNIGHT: + case SHAMAN: + case BEASTLORD: + case BERSERKER: + return true; + } + break; + case OGRE: + switch (c) { + case WARRIOR: + case SHADOWKNIGHT: + case SHAMAN: + case BEASTLORD: + case BERSERKER: + return true; + } + break; + case HALFLING: + switch (c) { + case WARRIOR: + case CLERIC: + case PALADIN: + case RANGER: + case DRUID: + case ROGUE: + return true; + } + break; + case GNOME: + switch (c) { + case WARRIOR: + case CLERIC: + case PALADIN: + case SHADOWKNIGHT: + case ROGUE: + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + return true; + } + break; + case IKSAR: + switch (c) { + case WARRIOR: + case SHADOWKNIGHT: + case MONK: + case SHAMAN: + case NECROMANCER: + case BEASTLORD: + return true; + } + break; + case VAHSHIR: + switch (c) { + case WARRIOR: + case BARD: + case ROGUE: + case SHAMAN: + case BEASTLORD: + case BERSERKER: + return true; + } + break; + case FROGLOK: + switch (c) { + case WARRIOR: + case CLERIC: + case PALADIN: + case SHADOWKNIGHT: + case ROGUE: + case SHAMAN: + case NECROMANCER: + case WIZARD: + return true; + } + break; + case DRAKKIN: + switch (c) { + case WARRIOR: + case CLERIC: + case PALADIN: + case RANGER: + case SHADOWKNIGHT: + case DRUID: + case MONK: + case BARD: + case ROGUE: + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + return true; + } + break; + default: + break; } - return Result; + return false; } -bool Bot::IsBotNameAvailable(char *botName, std::string* errorMessage) { +bool Bot::IsValidName() +{ + std::string name = this->GetCleanName(); + return Bot::IsValidName(name); +} + +bool Bot::IsValidName(std::string& name) +{ + if (name.length() < 4) + return false; + if (!IsCharUpper(name[0])) + return false; + + for (int i = 1; i < name.length(); ++i) { + if (!IsCharLower(name[i]) && name[i] != '_') { + return false; + } + } + + return true; +} + +bool Bot::IsBotNameAvailable(const char *botName, std::string* errorMessage) { if (botName == "" || strlen(botName) > 15 || !database.CheckNameFilter(botName) || !database.CheckUsedName(botName)) return false; @@ -2386,7 +2385,9 @@ void Bot::BotRangedAttack(Mob* other) { return; SendItemAnimation(other, Ammo, SkillArchery); - DoArcheryAttackDmg(GetTarget(), rangedItem, ammoItem); + //DoArcheryAttackDmg(GetTarget(), rangedItem, ammoItem); + DoArcheryAttackDmg(other, rangedItem, ammoItem); // watch + //break invis when you attack if(invisible) { Log.Out(Logs::Detail, Logs::Combat, "Removing invisibility due to melee attack."); @@ -2655,13 +2656,30 @@ float Bot::GetMaxMeleeRangeToTarget(Mob* target) { // AI Processing for the Bot object void Bot::AI_Process() { - if(!IsAIControlled()) + if (!IsAIControlled()) + return; + if (GetPauseAI()) return; uint8 botClass = GetClass(); uint8 botLevel = GetLevel(); - if(IsCasting() && (botClass != BARD)) - return; + + if (IsCasting()) { + if ( + IsHealRotationMember() && + m_member_of_heal_rotation->CastingOverride() && + m_member_of_heal_rotation->CastingTarget() != nullptr && + m_member_of_heal_rotation->CastingReady() && + m_member_of_heal_rotation->CastingMember() == this && + !m_member_of_heal_rotation->MemberIsCasting(this) + ) + InterruptSpell(); + else if (botClass != BARD) + return; + } + else if (IsHealRotationMember()) { + m_member_of_heal_rotation->SetMemberIsCasting(this, false); + } // A bot wont start its AI if not grouped if(!GetBotOwner() || !IsGrouped() || GetAppearance() == eaDead) @@ -2684,13 +2702,23 @@ void Bot::AI_Process() { return; } - if(GetHealRotationActive() && GetHealRotationTarget() && !GetHasHealedThisCycle() && GetHealRotationNextHealTime() < Timer::GetCurrentTime()) { - if(AIHealRotation(GetHealRotationTarget(), GetHealRotationUseFastHeals())) { - SetHasHealedThisCycle(true); - NotifyNextHealRotationMember(); + if(IsMyHealRotationSet()) { + Mob* delete_me = HealRotationTarget(); + if (AIHealRotation(HealRotationTarget(), UseHealRotationFastHeals())) { +#if (EQDEBUG >= 12) + Log.Out(Logs::General, Logs::Error, "Bot::AI_Process() - Casting succeeded (m: %s, t: %s) : AdvHR(true)", GetCleanName(), ((delete_me) ? (delete_me->GetCleanName()) : ("nullptr"))); +#endif + m_member_of_heal_rotation->SetMemberIsCasting(this); + m_member_of_heal_rotation->UpdateTargetHealingStats(HealRotationTarget()); + AdvanceHealRotation(); + } + else { +#if (EQDEBUG >= 12) + Log.Out(Logs::General, Logs::Error, "Bot::AI_Process() - Casting failed (m: %s, t: %s) : AdvHR(false)", GetCleanName(), ((delete_me) ? (delete_me->GetCleanName()) : ("nullptr"))); +#endif + m_member_of_heal_rotation->SetMemberIsCasting(this, false); + AdvanceHealRotation(false); } - else - NotifyNextHealRotationMember(true); } if(GetHasBeenSummoned()) { @@ -3584,48 +3612,6 @@ uint32 Bot::GetBotItemsCount(std::string *errorMessage) { return atoi(row[0]); } -bool Bot::MesmerizeTarget(Mob* target) { - bool Result = false; - if(target) { - int mezid = 0; - int mezlevel = GetLevel(); - if(mezlevel >= 69) - mezid = 5520; - else if(mezlevel == 68) - mezid = 8035; - else if(mezlevel == 67) - mezid = 5503; - else if(mezlevel >= 64) - mezid = 3358; - else if(mezlevel == 63) - mezid = 3354; - else if(mezlevel >= 61) - mezid = 3341; - else if(mezlevel == 60) - mezid = 2120; - else if(mezlevel == 59) - mezid = 1692; - else if(mezlevel >= 54) - mezid = 1691; - else if(mezlevel >= 47) - mezid = 190; - else if(mezlevel >= 30) - mezid = 188; - else if(mezlevel >= 13) - mezid = 187; - else if(mezlevel >= 2) - mezid = 292; - if(mezid > 0) { - uint32 DontRootMeBeforeTime = 0; - CastSpell(mezid, target->GetID(), 1, -1, -1, &DontRootMeBeforeTime); - target->SetDontRootMeBefore(DontRootMeBeforeTime); - Result = true; - } - } - - return Result; -} - void Bot::SetLevel(uint8 in_level, bool command) { if(in_level > 0) Mob::SetLevel(in_level, command); @@ -3911,7 +3897,7 @@ std::list Bot::GetBotList(uint32 botOwnerCharacterID, std::st if(botOwnerCharacterID == 0) return ownersBots; - std::string query = StringFormat("SELECT `bot_id`, `name`, `class`, `level`, `race` FROM `bot_data` WHERE `owner_id` = '%u'", botOwnerCharacterID); + std::string query = StringFormat("SELECT `bot_id`, `name`, `class`, `level`, `race`, `gender` FROM `bot_data` WHERE `owner_id` = '%u'", botOwnerCharacterID); auto results = database.QueryDatabase(query); if(!results.Success()) { *errorMessage = std::string(results.ErrorMessage()); @@ -3925,6 +3911,7 @@ std::list Bot::GetBotList(uint32 botOwnerCharacterID, std::st availableBot.BotClass = atoi(row[2]); availableBot.BotLevel = atoi(row[3]); availableBot.BotRace = atoi(row[4]); + availableBot.BotGender = atoi(row[5]); ownersBots.push_back(availableBot); } return ownersBots; @@ -3954,182 +3941,6 @@ std::list Bot::ListSpawnedBots(uint32 characterID, std::string* return spawnedBots; } -void Bot::SaveBotGroup(Group* botGroup, std::string botGroupName, std::string* errorMessage) -{ - if(!botGroup || botGroupName.empty()) - return; - - Mob* tempGroupLeader = botGroup->GetLeader(); - if(!tempGroupLeader->IsBot()) - return; - - uint32 botGroupId = 0; - uint32 botGroupLeaderBotId = tempGroupLeader->CastToBot()->GetBotID(); - std::string query = StringFormat("INSERT INTO `bot_groups` (`group_leader_id`, `group_name`) VALUES (%u, '%s')", botGroupLeaderBotId, botGroupName.c_str()); - auto results = database.QueryDatabase(query); - if(!results.Success()) { - *errorMessage = std::string(results.ErrorMessage()); - return; - } - - botGroupId = results.LastInsertedID(); - if(botGroupId == 0) - return; - - for(int groupMemberIndex = 0; groupMemberIndex < botGroup->GroupCount(); groupMemberIndex++) { - Mob* tempBot = botGroup->members[groupMemberIndex]; - if(!tempBot || !tempBot->IsBot()) - continue; - - uint32 botGroupMemberBotId = tempBot->CastToBot()->GetBotID(); - query = StringFormat("INSERT INTO `bot_group_members` (`groups_index`, `bot_id`) VALUES (%u, %u)", botGroupId, botGroupMemberBotId); - results = database.QueryDatabase(query); - if(!results.Success()) - *errorMessage = std::string(results.ErrorMessage()); - } -} - -void Bot::DeleteBotGroup(std::string botGroupName, std::string* errorMessage) { - if(botGroupName.empty()) - return; - - uint32 botGroupId = GetBotGroupIdByBotGroupName(botGroupName, errorMessage); - if(!errorMessage->empty() || botGroupId== 0) - return; - - std::string query = StringFormat("DELETE FROM `bot_group_members` WHERE `groups_index` = %u", botGroupId); - auto results = database.QueryDatabase(query); - if(!results.Success()) { - *errorMessage = std::string(results.ErrorMessage()); - return; - } - - query = StringFormat("DELETE FROM `bot_groups` WHERE `groups_index` = %u", botGroupId); - results = database.QueryDatabase(query); - if(!results.Success()) - *errorMessage = std::string(results.ErrorMessage()); -} - -std::list Bot::LoadBotGroup(std::string botGroupName, std::string* errorMessage) { - std::list botGroup; - if(botGroupName.empty()) - return botGroup; - - uint32 botGroupId = GetBotGroupIdByBotGroupName(botGroupName, errorMessage); - if(botGroupId == 0) - return botGroup; - - std::string query = StringFormat("SELECT `bot_id` FROM `bot_group_members` WHERE `groups_index` = %u", botGroupId); - auto results = database.QueryDatabase(query); - if(!results.Success()) { - *errorMessage = std::string(results.ErrorMessage()); - return botGroup; - } - - for(auto row = results.begin(); row != results.end(); ++row) { - BotGroup tempBotGroup; - tempBotGroup.BotGroupID = botGroupId; - tempBotGroup.BotID = atoi(row[0]); - botGroup.push_back(tempBotGroup); - } - return botGroup; -} - -std::list Bot::GetBotGroupListByBotOwnerCharacterId(uint32 botOwnerCharacterId, std::string* errorMessage) { - std::list botGroups; - if(botOwnerCharacterId == 0) - return botGroups; - - std::string query = StringFormat("SELECT `group_name`, `group_leader_name` FROM `vw_bot_groups` WHERE `owner_id` = %u", botOwnerCharacterId); - auto results = database.QueryDatabase(query); - if(!results.Success()) { - *errorMessage = std::string(results.ErrorMessage()); - return botGroups; - } - - for(auto row = results.begin(); row != results.end(); ++row) { - BotGroupList botGroupList; - botGroupList.BotGroupName = std::string(row[0]); - botGroupList.BotGroupLeaderName = std::string(row[1]); - botGroups.push_back(botGroupList); - } - return botGroups; -} - -bool Bot::DoesBotGroupNameExist(std::string botGroupName) { - if(botGroupName.empty()) - return false; - - std::string query = StringFormat("SELECT `groups_index` FROM `vw_bot_groups` WHERE `group_name` = '%s'", botGroupName.c_str()); - auto results = database.QueryDatabase(query); - if (!results.Success() || results.RowCount() == 0) - return false; - - for(auto row = results.begin(); row != results.end(); ++row) { - uint32 tempBotGroupId = atoi(row[0]); - std::string tempBotGroupName = std::string(row[1]); - if (botGroupName == tempBotGroupName && tempBotGroupId != 0) - return true; - } - - return false; -} - -uint32 Bot::CanLoadBotGroup(uint32 botOwnerCharacterId, std::string botGroupName, std::string* errorMessage) { - if(botOwnerCharacterId == 0 || botGroupName.empty()) - return 0; - - std::string query = StringFormat("SELECT `groups_index`, `group_name` FROM `vw_bot_groups` WHERE `owner_id` = %u", botOwnerCharacterId); - auto results = database.QueryDatabase(query); - if(!results.Success()) { - *errorMessage = std::string(results.ErrorMessage()); - return 0; - } - - if(results.RowCount() == 0) - return 0; - - for(auto row = results.begin(); row != results.end(); ++row) { - uint32 tempBotGroupId = atoi(row[0]); - std::string tempBotGroupName = std::string(row[1]); - if(botGroupName == tempBotGroupName) - return tempBotGroupId; - } - - return 0; -} - -uint32 Bot::GetBotGroupIdByBotGroupName(std::string botGroupName, std::string* errorMessage) { - if(botGroupName.empty()) - return 0; - - std::string query = StringFormat("SELECT `groups_index` FROM `vw_bot_groups` WHERE `group_name` = '%s'", botGroupName.c_str()); - auto results = database.QueryDatabase(query); - if(!results.Success()) { - *errorMessage = std::string(results.ErrorMessage()); - return 0; - } - - if (results.RowCount() == 0) - return 0; - - auto row = results.begin(); - return atoi(row[0]); -} - -uint32 Bot::GetBotGroupLeaderIdByBotGroupName(std::string botGroupName) { - if(botGroupName.empty()) - return 0; - - std::string query = StringFormat("SELECT `group_leader_id` FROM `vw_bot_groups` WHERE `group_name` = '%s'", botGroupName.c_str()); - auto results = database.QueryDatabase(query); - if (!results.Success() || results.RowCount() == 0) - return 0; - - auto row = results.begin(); - return atoi(row[0]); -} - uint32 Bot::AllowedBotSpawns(uint32 botOwnerCharacterID, std::string* errorMessage) { if(botOwnerCharacterID == 0) return 0; @@ -4424,120 +4235,6 @@ void Bot::BotTradeAddItem(uint32 id, const ItemInst* inst, int16 charges, uint32 this->BotAddEquipItem(lootSlot, id); } -bool Bot::Bot_Command_Resist(int resisttype, int level) { - int resistid = 0; - switch(resisttype) { - case 1: // Poison Cleric - if(level >= 30) - resistid = 62; - else if(level >= 6) - resistid = 227; - break; - case 2: // Disease Cleric - if(level >= 36) - resistid = 63; - else if(level >= 11) - resistid = 226; - break; - case 3: // Fire Cleric - if(level >= 33) - resistid = 60; - else if(level >= 8) - resistid = 224; - break; - case 4: // Cold Cleric - if(level >= 38) - resistid = 61; - else if(level >= 13) - resistid = 225; - break; - case 5: // Magic Cleric - if(level >= 43) - resistid = 64; - else if(level >= 16) - resistid = 228; - break; - case 6: // Magic Enchanter - if(level >= 37) - resistid = 64; - else if(level >= 17) - resistid = 228; - break; - case 7: // Poison Druid - if(level >= 44) - resistid = 62; - else if(level >= 19) - resistid = 227; - break; - case 8: // Disease Druid - if(level >= 44) - resistid = 63; - else if(level >= 19) - resistid = 226; - break; - case 9: // Fire Druid - if(level >= 20) - resistid = 60; - else if(level >= 1) - resistid = 224; - break; - case 10: // Cold Druid - if(level >= 30) - resistid = 61; - else if(level >= 9) - resistid = 225; - break; - case 11: // Magic Druid - if(level >= 49) - resistid = 64; - else if(level >= 34) - resistid = 228; - break; - case 12: // Poison Shaman - if(level >= 35) - resistid = 62; - else if(level >= 20) - resistid = 227; - break; - case 13: // Disease Shaman - if(level >= 30) - resistid = 63; - else if(level >= 8) - resistid = 226; - break; - case 14: // Fire Shaman - if(level >= 27) - resistid = 60; - else if(level >= 5) - resistid = 224; - break; - case 15: // Cold Shaman - if(level >= 24) - resistid = 61; - else if(level >= 1) - resistid = 225; - break; - case 16: // Magic Shaman - if(level >= 43) - resistid = 64; - else if(level >= 19) - resistid = 228; - break; - } - - if(resistid > 0) { - Group* g = GetGroup(); - if(g) { - for(int k = 0; k < MAX_GROUP_MEMBERS; k++) { - if(g->members[k]) - SpellOnTarget(resistid, g->members[k]); - } - return true; - } - } - return false; -} - bool Bot::RemoveBotFromGroup(Bot* bot, Group* group) { bool Result = false; if(bot && group) { @@ -4584,230 +4281,6 @@ bool Bot::AddBotToGroup(Bot* bot, Group* group) { return Result; } -bool Bot::BotGroupCreate(std::string botGroupLeaderName) { - bool Result = false; - if(!botGroupLeaderName.empty()) { - Bot* botGroupLeader = entity_list.GetBotByBotName(botGroupLeaderName); - if(botGroupLeader) - Result = BotGroupCreate(botGroupLeader); - } - return Result; -} - -bool Bot::BotGroupCreate(Bot* botGroupLeader) { - bool Result = false; - if(botGroupLeader && !botGroupLeader->HasGroup()) { - Group* newGroup = new Group(botGroupLeader); - if(newGroup) { - entity_list.AddGroup(newGroup); - database.SetGroupID(botGroupLeader->GetName(), newGroup->GetID(), botGroupLeader->GetBotID()); - database.SetGroupLeaderName(newGroup->GetID(), botGroupLeader->GetName()); - botGroupLeader->SetFollowID(botGroupLeader->GetBotOwner()->GetID()); - Result = true; - } - } - return Result; -} - -bool Bot::Bot_Command_CharmTarget(int charmtype, Mob *target) { - int charmid = 0; - int charmlevel = GetLevel(); - if(target) { - switch(charmtype) { - case 1: // Enchanter - if((charmlevel >= 64) && (charmlevel <= 75)) - charmid = 3355; - else if((charmlevel >= 62) && (charmlevel <= 63)) - charmid = 3347; - else if((charmlevel >= 60) && (charmlevel <= 61)) - charmid = 1707; - else if((charmlevel >= 53) && (charmlevel <= 59)) - charmid = 1705; - else if((charmlevel >= 37) && (charmlevel <= 52)) - charmid = 183; - else if((charmlevel >= 23) && (charmlevel <= 36)) - charmid = 182; - else if((charmlevel >= 11) && (charmlevel <= 22)) - charmid = 300; - break; - case 2: // Necromancer - if((charmlevel >= 60) && (charmlevel <= 75)) - charmid = 1629; - else if((charmlevel >=47) && (charmlevel <= 59)) - charmid = 198; - else if((charmlevel >= 31) && (charmlevel <= 46)) - charmid = 197; - else if((charmlevel >= 18) && (charmlevel <= 30)) - charmid = 196; - break; - case 3: // Druid - if((charmlevel >= 63) && (charmlevel <= 75)) - charmid = 3445; - else if((charmlevel >= 55) && (charmlevel <= 62)) - charmid = 1556; - else if((charmlevel >= 52) && (charmlevel <= 54)) - charmid = 1553; - else if((charmlevel >= 43) && (charmlevel <= 51)) - charmid = 142; - else if((charmlevel >= 33) && (charmlevel <= 42)) - charmid = 141; - else if((charmlevel >= 23) && (charmlevel <= 32)) - charmid = 260; - else if((charmlevel >= 13) && (charmlevel <= 22)) - charmid = 242; - break; - } - if(charmid > 0) { - uint32 DontRootMeBeforeTime = 0; - CastSpell(charmid, target->GetID(), 1, -1, -1, &DontRootMeBeforeTime); - target->SetDontRootMeBefore(DontRootMeBeforeTime); - return true; - } - } - return false; -} - -bool Bot::Bot_Command_DireTarget(int diretype, Mob *target) { - int direid = 0; - int direlevel = GetLevel(); - if(target) { - switch(diretype) { - case 1: // Enchanter - if(direlevel >= 65) - direid = 5874; - else if(direlevel >= 55) - direid = 2761; - break; - case 2: // Necromancer - if(direlevel >= 65) - direid = 5876; - else if(direlevel >= 55) - direid = 2759; - break; - case 3: // Druid - if(direlevel >= 65) - direid = 5875; - else if(direlevel >= 55) - direid = 2760; - break; - } - if(direid > 0) { - uint32 DontRootMeBeforeTime = 0; - CastSpell(direid, target->GetID(), 1, -1, -1, &DontRootMeBeforeTime); - target->SetDontRootMeBefore(DontRootMeBeforeTime); - return true; - } - } - return false; -} - -bool Bot::Bot_Command_CalmTarget(Mob *target) { - if(target) { - int calmid = 0; - int calmlevel = GetLevel(); - if((calmlevel >= 67) && (calmlevel <= 75)) - calmid = 5274; - else if((calmlevel >= 62) && (calmlevel <= 66)) - calmid = 3197; - else if((calmlevel >= 35) && (calmlevel <= 61)) - calmid = 45; - else if((calmlevel >= 18) && (calmlevel <= 34)) - calmid = 47; - else if((calmlevel >= 6) && (calmlevel <= 17)) - calmid = 501; - else if((calmlevel >= 1) && (calmlevel <= 5)) - calmid = 208; - if(calmid > 0) { - uint32 DontRootMeBeforeTime = 0; - CastSpell(calmid, target->GetID(), 1, -1, -1, &DontRootMeBeforeTime); - target->SetDontRootMeBefore(DontRootMeBeforeTime); - return true; - } - } - return false; -} - -bool Bot::Bot_Command_RezzTarget(Mob *target) { - if(target) { - int rezid = 0; - int rezlevel = GetLevel(); - if(rezlevel >= 56) - rezid = 1524; - else if(rezlevel >= 47) - rezid = 392; - else if(rezlevel >= 42) - rezid = 2172; - else if(rezlevel >= 37) - rezid = 388; - else if(rezlevel >= 32) - rezid = 2171; - else if(rezlevel >= 27) - rezid = 391; - else if(rezlevel >= 22) - rezid = 2170; - else if(rezlevel >= 18) - rezid = 2169; - if(rezid > 0) { - uint32 DontRootMeBeforeTime = 0; - CastSpell(rezid, target->GetID(), 1, -1, -1, &DontRootMeBeforeTime); - target->SetDontRootMeBefore(DontRootMeBeforeTime); - return true; - } - } - return false; -} - -bool Bot::Bot_Command_Cure(int curetype, int level) { - int cureid = 0; - switch(curetype) { - case 1: // Poison - if(level >= 58) - cureid = 1525; - else if(level >= 48) - cureid = 97; - else if(level >= 22) - cureid = 95; - else if(level >= 1) - cureid = 203; - break; - case 2: // Disease - if(level >= 51) - cureid = 3693; - else if(level >= 28) - cureid = 96; - else if(level >= 4) - cureid = 213; - break; - case 3: // Curse - if(level >= 54) - cureid = 2880; - else if(level >= 38) - cureid = 2946; - else if(level >= 23) - cureid = 4057; - else if(level >= 8) - cureid = 4056; - break; - case 4: // Blindness - if(level >= 3) - cureid = 212; - break; - } - - if(cureid > 0) { - Group* g = GetGroup(); - if(g) { - for(int k = 0; k < MAX_GROUP_MEMBERS; k++) { - if(g->members[k]) - SpellOnTarget(cureid, g->members[k]); - } - return true; - } - } - - return false; -} - // Completes a trade with a client bot owner void Bot::FinishTrade(Client* client, BotTradeType tradeType) { if(client && !client->GetTradeskillObject() && (client->trade->state != Trading)) { @@ -5117,8 +4590,7 @@ bool Bot::Death(Mob *killerMob, int32 damage, uint16 spell_id, SkillUseTypes att } } - if(GetInHealRotation()) - GetHealRotationLeader()->RemoveHealRotationMember(this); + LeaveHealRotationMemberPool(); entity_list.RemoveBot(this->GetID()); return true; @@ -8164,7 +7636,7 @@ void Bot::CalcRestState() { int32 Bot::LevelRegen() { int level = GetLevel(); - bool bonus = GetRaceBitmask(_baseRace) & RuleI(Character, BaseHPRegenBonusRaces); + bool bonus = GetPlayerRaceBit(_baseRace) & RuleI(Character, BaseHPRegenBonusRaces); uint8 multiplier1 = bonus ? 2 : 1; int32 hp = 0; if (level < 51) { @@ -8429,8 +7901,7 @@ void Bot::Camp(bool databaseSave) { if(IsGrouped()) RemoveBotFromGroup(this, GetGroup()); - if(GetInHealRotation()) - GetHealRotationLeader()->RemoveHealRotationMember(this); + LeaveHealRotationMemberPool(); if(databaseSave) Save(); @@ -8562,91 +8033,6 @@ bool Bot::CalculateNewPosition2(float x, float y, float z, float speed, bool che return MakeNewPositionAndSendUpdate(x, y, z, speed, checkZ); } -void Bot::BotGroupOrderFollow(Group* group, Client* client) { - if(group && client) { - Mob* groupLeader = group->GetLeader(); - if(groupLeader) { - for(int i = 0; i< MAX_GROUP_MEMBERS; i++) { - if(group->members[i] && group->members[i]->IsBot()) { - Bot* botGroupMember = group->members[i]->CastToBot(); - if(botGroupMember && botGroupMember->GetBotOwnerCharacterID() == client->CharacterID()) { - if(group->IsLeader(botGroupMember) && botGroupMember->GetBotOwner()) { - botGroupMember->SetFollowID(botGroupMember->GetBotOwner()->GetID()); - if(botGroupMember->GetBotOwner()) - botGroupMember->BotGroupSay(botGroupMember, "Following %s.", botGroupMember->GetBotOwner()->GetName()); - } else { - botGroupMember->SetFollowID(groupLeader->GetID()); - botGroupMember->BotGroupSay(botGroupMember, "Following %s.", groupLeader->GetCleanName()); - } - - botGroupMember->WipeHateList(); - if(botGroupMember->HasPet() && botGroupMember->GetPet()) - botGroupMember->GetPet()->WipeHateList(); - } - } - } - } - } -} - -void Bot::BotGroupOrderGuard(Group* group, Client* client) { - if(group && client) { - for(int i = 0; i< MAX_GROUP_MEMBERS; i++) { - if(group->members[i] && group->members[i]->IsBot()) { - Bot* botGroupMember = group->members[i]->CastToBot(); - if(botGroupMember && botGroupMember->GetBotOwnerCharacterID() == client->CharacterID()) { - botGroupMember->SetFollowID(0); - botGroupMember->BotGroupSay(botGroupMember, "Guarding here."); - botGroupMember->WipeHateList(); - if(botGroupMember->HasPet() && botGroupMember->GetPet()) - botGroupMember->GetPet()->WipeHateList(); - } - } - } - } -} - -void Bot::BotGroupOrderAttack(Group* group, Mob* target, Client* client) { - if(group && target) { - Mob* groupLeader = group->GetLeader(); - if(groupLeader) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(group->members[i] && group->members[i]->IsBot()) { - Bot* botGroupMember = group->members[i]->CastToBot(); - if(botGroupMember->GetBotOwnerCharacterID() == client->CharacterID()) { - botGroupMember->WipeHateList(); - botGroupMember->AddToHateList(target, 1); - if(botGroupMember->HasPet() && botGroupMember->GetPet()) { - botGroupMember->GetPet()->WipeHateList(); - botGroupMember->GetPet()->AddToHateList(target, 1); - } - } - } - } - } - } -} - -void Bot::BotGroupSummon(Group* group, Client* client) { - if(group) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(group->members[i] && group->members[i]->IsBot()) { - Bot* botMember = group->members[i]->CastToBot(); - if(botMember->GetBotOwnerCharacterID() == client->CharacterID()) { - botMember->SetTarget(botMember->GetBotOwner()); - botMember->WipeHateList(); - botMember->Warp(glm::vec3(botMember->GetBotOwner()->GetPosition())); - if(botMember->HasPet() && botMember->GetPet()) { - botMember->GetPet()->SetTarget(botMember); - botMember->GetPet()->WipeHateList(); - botMember->GetPet()->Warp(glm::vec3(botMember->GetBotOwner()->GetPosition())); - } - } - } - } - } -} - Bot* Bot::GetBotByBotClientOwnerAndBotName(Client* c, std::string botName) { Bot* Result = 0; if(c) { @@ -9157,11 +8543,11 @@ void Bot::CalcBotStats(bool showtext) { return; if(showtext) { - GetBotOwner()->Message(15, "Bot updating..."); + GetBotOwner()->Message(15, "Updating %s...", GetCleanName()); } if(!IsValidRaceClassCombo()) { - GetBotOwner()->Message(15, "A %s - %s bot was detected. Is this Race/Class combination allowed?.", GetRaceName(GetRace()), GetEQClassName(GetClass(), GetLevel())); + GetBotOwner()->Message(15, "A %s - %s bot was detected. Is this Race/Class combination allowed?.", GetRaceIDName(GetRace()), GetClassIDName(GetClass(), GetLevel())); GetBotOwner()->Message(15, "Previous Bots Code releases did not check Race/Class combinations during create."); GetBotOwner()->Message(15, "Unless you are experiencing heavy lag, you should delete and remake this bot."); } @@ -9191,7 +8577,7 @@ void Bot::CalcBotStats(bool showtext) { AI_AddNPCSpells(this->GetBotSpellID()); if(showtext) { - GetBotOwner()->Message(15, "I'm updated."); + GetBotOwner()->Message(15, "%s has been updated.", GetCleanName()); GetBotOwner()->Message(15, "Level: %i HP: %i AC: %i Mana: %i STR: %i STA: %i DEX: %i AGI: %i INT: %i WIS: %i CHA: %i", GetLevel(), max_hp, GetAC(), max_mana, GetSTR(), GetSTA(), GetDEX(), GetAGI(), GetINT(), GetWIS(), GetCHA()); GetBotOwner()->Message(15, "Resists-- Magic: %i, Poison: %i, Fire: %i, Cold: %i, Disease: %i, Corruption: %i.",GetMR(),GetPR(),GetFR(),GetCR(),GetDR(),GetCorrup()); // Test Code @@ -9233,3588 +8619,6 @@ bool Bot::GroupHasClass(Group* group, uint8 classId) { return result; } -void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { - // All bot command processing occurs here now instead of in command.cpp - - // TODO: Log any possible error messages as most of these will be MySQL error messages. - std::string TempErrorMessage; - - if(sep->arg[1][0] == '\0') { - c->Message(13, "Bad argument, type #bot help"); - return; - } - if(!strcasecmp( sep->arg[1], "help") && !strcasecmp( sep->arg[2], "\0")){ - c->Message(0, "List of commands availables for bots:"); - c->Message(0, "#bot help - show this"); - c->Message(0, "#bot create [name] [class (id)] [race (id)] [model (male/female)] - create a permanent bot. See #bot help create."); - c->Message(0, "#bot help create - show all the race/class id. (make it easier to create bots)"); - c->Message(0, "#bot delete - completely destroy forever the targeted bot and all its items."); - c->Message(0, "#bot list [all/class(1-16)] - list all of your bots or list by class. Classes: 1(WAR), 2(CLR), 3(PAL), 4(RNG), 5(SHD), 6(DRU), 7(MNK), 8(BRD), 9(ROG), 10(SHM), 11(NEC), 12(WIZ), 13(MAG), 14(ENC), 15(BST), 16(BER)"); - c->Message(0, "#bot spawn [bot name] - spawn a bot from it's name (use list to see all the bots). "); - c->Message(0, "#bot inventory list - show the inventory (and the slots IDs) of the targeted bot."); - c->Message(0, "#bot inventory remove [slotid] - remove the item at the given slot in the inventory of the targeted bot."); - c->Message(0, "#bot update - you must type that command once you gain a level."); - c->Message(0, "#bot summon - It will summon your targeted bot to you."); - c->Message(0, "#bot mez - If you're grouped with an Enchanter, he will mesmerize your target."); - c->Message(0, "#bot picklock - You must have a targeted Rogue bot in your group and be right on the door."); - c->Message(0, "#bot cure [poison|disease|curse|blindness] Cleric has most options"); - c->Message(0, "#bot bind - You must have a Cleric in your group to get Bind Affinity cast on you."); - c->Message(0, "#bot track - look at mobs in the zone (Ranger has options)"); - c->Message(0, "#bot target calm - attempts to pacify your target mob."); - c->Message(0, "#bot evac - transports your group to safe location in the current zone (bots are lost)"); - c->Message(0, "#bot resurrect - Your Cleric bot will resurrect your targeted player corpse."); - c->Message(0, "#bot corpse summon - Necromancers summon your targeted player's corpses."); - c->Message(0, "#bot lore - cast Identify on the item on your mouse pointer."); - c->Message(0, "#bot speed - Bots will cast Spirit of Wolf on you (Druid has options)"); - c->Message(0, "#bot invis - Bot invisiblity (must have proper class in group)"); - c->Message(0, "#bot levitate - Bot levitation (must have proper class in group)"); - c->Message(0, "#bot resist - Bot resist buffs (must have proper class in group)"); - c->Message(0, "#bot rune - Enchanter bot casts Rune spell on you"); - c->Message(0, "#bot shrink - Shaman or Beastlord will shrink target"); - c->Message(0, "#bot endureb - Bot enduring breath (must have proper class in group)"); - c->Message(0, "#bot charm - (must have proper class in group)"); - c->Message(0, "#bot dire charm - (must have proper class in group)"); - c->Message(0, "#bot pet remove - (remove pet before charm)"); - c->Message(0, "#bot gate - Druid or Wizard bot will cast gate."); - c->Message(0, "#bot archery - Toggle Archery Skilled bots between using a Bow or using Melee weapons."); - c->Message(0, "#bot setpet [earth|water|air|fire|monster] - Select the pet type you want your Magician bot to use."); - c->Message(0, "#bot [giveitem|gi] - Gives your targeted bot the item you have on your cursor."); - c->Message(0, "#bot [augmentitem|ai] - Allows you to augment items for other classes. (You MUST have the Augmentation Sealer window filled)"); - c->Message(0, "#bot camp - Tells your bot to camp out of the game."); - c->Message(0, "#bot group help - Displays the commands available to manage any bots in your group."); - c->Message(0, "#bot botgroup help - Displays the commands available to manage bot ONLY groups."); - c->Message(0, "#bot mana [ | all] - Displays a mana report for all your spawned bots."); - c->Message(0, "#bot setfollowdistance ### - sets target bots follow distance to ### (ie 30 or 250)."); - c->Message(0, "#bot clearfollowdistance [ | spawned | all] - clears user-defined follow distance setting for bot target, spawned or all - includes spawned and unspawned."); - c->Message(0, "#bot [hair|haircolor|beard|beardcolor|face|eyes|heritage|tattoo|details ] - Change your bot's appearance."); - c->Message(0, "#bot armorcolor - #bot help armorcolor for info"); - c->Message(0, "#bot taunt [on|off] - Determines whether or not your targeted bot will taunt."); - c->Message(0, "#bot stance [name] [stance (id)|list] - Sets/lists stance for named bot (Passive = 0, Balanced = 1, Efficient = 2, Reactive = 3, Aggressive = 4, Burn = 5, BurnAE = 6)"); - c->Message(0, "#bot defensive [bot name] - Causes Warrior, Shadow Knight, or Paladin bot to use their defensive discipline or buff."); - c->Message(0, "#bot healrotation help - Displays the commands available to manage bot heal rotations."); - c->Message(0, "#bot pull [] [target] - Bot will attempt to pull your target NPC."); - c->Message(0, "#bot setinspectmessage - Copies your inspect message to a targeted bot that you own."); - c->Message(0, "#bot bardoutofcombat [on|off] - Determines whether Bard bots use out of combat songs."); - c->Message(0, "#bot showhelm [on|off] - Determines whether or not your targeted bot's helmet will show. (Requires a respawn to take effect)"); - return; - } - - // pull - if(!strcasecmp(sep->arg[1], "pull")) { - Mob *target = c->GetTarget(); - if(target == nullptr || target == c || target->IsBot() || (target->IsPet() && target->GetOwner() && target->GetOwner()->IsBot())) { - c->Message(15, "You must select a monster"); - return; - } - - if(c->IsGrouped()) { - bool haspuller = false; - Group *g = c->GetGroup(); - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(g && g->members[i] && g->members[i]->IsBot() && !strcasecmp(g->members[i]->GetName() , sep->arg[2])) { - haspuller = true; - Mob *puller = g->members[i]; - if (puller->CastToBot()->IsArcheryRange(target)) { - puller->CastToBot()->BotGroupSay(puller->CastToBot(), "Trying to pull %s.", target->GetCleanName()); - puller->CastToBot()->BotRangedAttack(target); - } else { - puller->CastToBot()->BotGroupSay(puller->CastToBot(), "%s is out of range.", target->GetCleanName()); - } - } - } - if(!haspuller) { - c->Message(15, "You must have an Puller in your group."); - } - } - return; - } - - // added Bot follow distance - SetFollowDistance - if(!strcasecmp(sep->arg[1], "setfollowdistance")) { - if((c->GetTarget() == nullptr) || (c->GetTarget() == c) || (!c->GetTarget()->IsBot()) || (c->GetTarget()->CastToBot()->GetBotOwner() != c)) { - c->Message(15, "You must target a bot you own!"); - } - else { - uint32 BotFollowDistance = atoi(sep->arg[2]); - c->GetTarget()->SetFollowDistance(BotFollowDistance); - } - - return; - } - - if (!strcasecmp(sep->arg[1], "clearfollowdistance")) { - bool case_all = !strcasecmp(sep->arg[2], "all"); - bool case_spawned = !strcasecmp(sep->arg[2], "spawned"); - if (case_all || case_spawned) { - if (case_all) { - std::string query = StringFormat( - "UPDATE `bot_data`" - " SET `follow_distance` = '%u'" - " WHERE `owner_id` = '%u'", - BOT_DEFAULT_FOLLOW_DISTANCE, - c->CharacterID() - ); - auto results = database.QueryDatabase(query); - if (!results.Success()) - return; - } - - std::list spawnedBots = entity_list.GetBotsByBotOwnerCharacterID(c->CharacterID()); - if (!spawnedBots.empty()) { - for (std::list::iterator botsListItr = spawnedBots.begin(); botsListItr != spawnedBots.end(); ++botsListItr) { - Bot* tempBot = *botsListItr; - if (tempBot) { - tempBot->SetFollowDistance(BOT_DEFAULT_FOLLOW_DISTANCE); - } - } - } - } - else if ((c->GetTarget() == nullptr) || (c->GetTarget() == c) || (!c->GetTarget()->IsBot()) || (c->GetTarget()->CastToBot()->GetBotOwner() != c)) { - c->Message(15, "You must target a bot you own!"); - } - else { - c->GetTarget()->SetFollowDistance(BOT_DEFAULT_FOLLOW_DISTANCE); - } - - return; - } - - //bot armor colors - if(!strcasecmp(sep->arg[1], "armorcolor")) { - if(c->GetTarget() && c->GetTarget()->IsBot() && (c->GetTarget()->CastToBot()->GetBotOwner() == c)) { - - if(sep->arg[2][0] == '\0' || sep->arg[3][0] == '\0' || sep->arg[4][0] == '\0' || sep->arg[5][0] == '\0') { - c->Message(0, "Usage: #bot armorcolor [slot] [red] [green] [blue] - use #bot help armorcolor for info"); - return; - } - - uint32 botid = c->GetTarget()->CastToBot()->GetBotID(); - std::string errorMessage; - - int setslot = atoi(sep->arg[2]); - uint8 red = atoi(sep->arg[3]); - uint8 green = atoi(sep->arg[4]); - uint8 blue = atoi(sep->arg[5]); - uint32 setcolor = (red << 16) | (green << 8) | blue; - std::string query; - if (setslot == -1) { - query = StringFormat( - "UPDATE `bot_inventories`" - " SET `inst_color` = %u" - " WHERE `slot_id`" - " IN (%u, %u, %u, %u, %u, %u, %u)" - " AND `bot_id` = %u", - setcolor, - MainHead, - MainArms, - MainWrist1, - MainHands, - MainChest, - MainLegs, - MainFeet, - botid - ); - auto results = database.QueryDatabase(query); - if (!results.Success()) - return; - - for (int i = MaterialHead; i <= MaterialFeet; ++i) { - c->GetTarget()->CastToBot()->SendWearChange(i); - } - } else { - query = StringFormat("UPDATE `bot_inventories` SET `inst_color` = %u WHERE `slot_id` = %i AND `bot_id` = %u", setcolor, setslot, botid); - auto results = database.QueryDatabase(query); - if (!results.Success()) - return; - - c->GetTarget()->CastToBot()->SendWearChange(Inventory::CalcMaterialFromSlot(setslot)); - } - - } - else { - c->Message(15, "You must target a bot you own to do this."); - } - return; - } - - if(!strcasecmp(sep->arg[1], "help") && !strcasecmp(sep->arg[2], "armorcolor")){ - c->Message(0, "-----------------#bot armorcolor help-----------------------------"); - c->Message(0, "Armor: -1(All), %u(Helm), %u(Arms), %u(Bracer), %u(Hands), %u(Chest/Robe), %u(Legs), %u(Boots)", - MainHead, MainArms, MainWrist1, MainHands, MainChest, MainLegs, MainFeet); - c->Message(0, "------------------------------------------------------------------"); - c->Message(0, "Color: [red] [green] [blue] (enter a number from 0-255 for each"); - c->Message(0, "------------------------------------------------------------------"); - c->Message(0, "Example: #bot armorcolor %u 0 255 0 - this would make the chest bright green", MainChest); - return; - } - - if(!strcasecmp(sep->arg[1], "augmentitem") || !strcasecmp(sep->arg[1], "ai")) { - AugmentItem_Struct* in_augment = new AugmentItem_Struct[sizeof(AugmentItem_Struct)]; - in_augment->container_slot = 1000; // - in_augment->augment_slot = -1; - Object::HandleAugmentation(c, in_augment, c->GetTradeskillObject()); - return; - } - - if(!strcasecmp(sep->arg[1], "giveitem") || !strcasecmp(sep->arg[1], "gi")) { - if(c->GetTarget() && c->GetTarget()->IsBot() && (c->GetTarget()->CastToBot()->GetBotOwner() == c)) { - Bot* targetedBot = c->GetTarget()->CastToBot(); - if(targetedBot) - targetedBot->FinishTrade(c, BotTradeClientNoDropNoTrade); - } - else - c->Message(15, "You must target a bot you own to do this."); - - return; - } - - if(!strcasecmp(sep->arg[1], "camp")) { - if(!strcasecmp(sep->arg[2], "all")) { - BotOrderCampAll(c); - } else { - if(c->GetTarget() && c->GetTarget()->IsBot() && (c->GetTarget()->CastToBot()->GetBotOwner()->CastToClient() == c)) { - Bot* targetedBot = c->GetTarget()->CastToBot(); - if(targetedBot) - targetedBot->Camp(); - } - else - c->Message(15, "You must target a bot you own to do this."); - } - - return; - } - - if(!strcasecmp(sep->arg[1], "create")) { - if(sep->arg[2][0] == '\0' || sep->arg[3][0] == '\0' || sep->arg[4][0] == '\0' || sep->arg[5][0] == '\0' || sep->arg[6][0] != '\0') { - c->Message(0, "Usage: #bot create [name] [class(id)] [race(id)] [gender (male/female|0/1)]"); - return; - } else if(strcasecmp(sep->arg[3],"1") && strcasecmp(sep->arg[3],"2") && strcasecmp(sep->arg[3],"3") && strcasecmp(sep->arg[3],"4") && strcasecmp(sep->arg[3],"5") && strcasecmp(sep->arg[3],"6") && strcasecmp(sep->arg[3],"7") && strcasecmp(sep->arg[3],"8") && strcasecmp(sep->arg[3],"9") && strcasecmp(sep->arg[3],"10") && strcasecmp(sep->arg[3],"11") && strcasecmp(sep->arg[3],"12") && strcasecmp(sep->arg[3],"13") && strcasecmp(sep->arg[3],"14") && strcasecmp(sep->arg[3],"15") && strcasecmp(sep->arg[3],"16")) { - c->Message(0, "Usage: #bot create [name] [class(id)] [race(id)] [gender (male/female|0/1)]"); - return; - } else if(strcasecmp(sep->arg[4],"1") && strcasecmp(sep->arg[4],"2") && strcasecmp(sep->arg[4],"3") && strcasecmp(sep->arg[4],"4") && strcasecmp(sep->arg[4],"5") && strcasecmp(sep->arg[4],"6") && strcasecmp(sep->arg[4],"7") && strcasecmp(sep->arg[4],"8") && strcasecmp(sep->arg[4],"9") && strcasecmp(sep->arg[4],"10") && strcasecmp(sep->arg[4],"11") && strcasecmp(sep->arg[4],"12") && strcasecmp(sep->arg[4],"330") && strcasecmp(sep->arg[4],"128") && strcasecmp(sep->arg[4],"130") && strcasecmp(sep->arg[4],"522")) { - c->Message(0, "Usage: #bot create [name] [class(1-16)] [race(1-12,128,130,330,522)] [gender (male/female|0/1)]"); - return; - } else if(strcasecmp(sep->arg[5],"male") && strcasecmp(sep->arg[5],"0") && strcasecmp(sep->arg[5],"female") && strcasecmp(sep->arg[5],"1")) { - c->Message(0, "Usage: #bot create [name] [class(1-16)] [race(1-12,128,130,330,522)] [gender (male/female|0/1)]"); - return; - } - - uint32 MaxBotCreate = RuleI(Bots, CreationLimit); - if(CreatedBotCount(c->CharacterID(), &TempErrorMessage) >= MaxBotCreate) { - c->Message(0, "You cannot create more than %i bots.", MaxBotCreate); - return; - } - - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - return; - } - - int gender = 0; - if(!strcasecmp(sep->arg[5], "female") || !strcasecmp(sep->arg[5], "1")) - gender = 1; - - if(!IsBotNameAvailable(sep->arg[2],&TempErrorMessage)) { - c->Message(0, "The name %s is already being used or is invalid. Please choose a different name.", sep->arg[2]); - return; - } - - NPCType DefaultNPCTypeStruct = CreateDefaultNPCTypeStructForBot(std::string(sep->arg[2]), std::string(), c->GetLevel(), atoi(sep->arg[4]), atoi(sep->arg[3]), gender); - Bot* NewBot = new Bot(DefaultNPCTypeStruct, c); - - if(NewBot) { - if(!NewBot->IsValidRaceClassCombo()) { - c->Message(0, "That Race/Class combination cannot be created."); - return; - } - - if(!NewBot->IsValidName()) { - c->Message(0, "%s has invalid characters. You can use only the A-Z, a-z and _ characters in a bot name.", NewBot->GetCleanName()); - return; - } - - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - return; - } - - if(!NewBot->Save()) - c->Message(0, "Unable to save %s as a bot.", NewBot->GetCleanName()); - else - c->Message(0, "%s saved as bot %u.", NewBot->GetCleanName(), NewBot->GetBotID()); - } - else - Log.Out(Logs::General, Logs::Error, "Error in #bot create, cannot find NewBot"); - return; - } - - if(!strcasecmp(sep->arg[1], "help") && !strcasecmp(sep->arg[2], "create") ){ - c->Message(0, "Classes: 1(WAR), 2(CLR), 3(PAL), 4(RNG), 5(SHD), 6(DRU), 7(MNK), 8(BRD), 9(ROG), 10(SHM), 11(NEC), 12(WIZ), 13(MAG), 14(ENC), 15(BST), 16(BER)"); - c->Message(0, "------------------------------------------------------------------"); - c->Message(0, "Races: 1(Human), 2(Barbarian), 3(Erudite), 4(Wood Elf), 5(High Elf), 6(Dark Elf), 7(Half Elf), 8(Dwarf), 9(Troll), 10(Ogre), 11(Halfling), 12(Gnome), 128(Iksar), 130(Vah Shir), 330(Froglok), 522(Drakkin)"); - c->Message(0, "------------------------------------------------------------------"); - c->Message(0, "Usage: #bot create [name] [class(1-16)] [race(1-12,128,130,330,522)] [gender(male/female)]"); - c->Message(0, "Example: #bot create Sneaky 9 6 male"); - return; - } - - if(!strcasecmp(sep->arg[1], "delete") ) { - if((c->GetTarget() == nullptr) || !c->GetTarget()->IsBot()) { - c->Message(15, "You must target a bot!"); - return; - } else if(c->GetTarget()->CastToBot()->GetBotOwnerCharacterID() != c->CharacterID()) { - c->Message(15, "You can't delete a bot that you don't own."); - return; - } - - if(c->GetTarget()->IsBot()) { - Bot* BotTargeted = c->GetTarget()->CastToBot(); - if(BotTargeted) { - BotTargeted->DeleteBot(&TempErrorMessage); - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - return; - } - - BotTargeted->Camp(false); - } - } - - return; - } - - if(!strcasecmp(sep->arg[1], "list")) { - bool listAll = true; - int iClass = atoi(sep->arg[2]); - if(iClass > 0 && iClass < 17) - listAll = false; - - std::list AvailableBots = GetBotList(c->CharacterID(), &TempErrorMessage); - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - return; - } - - if(!AvailableBots.empty()) { - for(std::list::iterator TempAvailableBotsList = AvailableBots.begin(); TempAvailableBotsList != AvailableBots.end(); ++TempAvailableBotsList) { - if(!listAll && TempAvailableBotsList->BotClass != iClass) - continue; - - c->Message(0, "Name: %s -- Class: %s -- Level: %u -- Race: %s", TempAvailableBotsList->BotName, ClassIdToString(TempAvailableBotsList->BotClass).c_str(), TempAvailableBotsList->BotLevel, RaceIdToString(TempAvailableBotsList->BotRace).c_str()); - } - } - else - c->Message(0, "You have no bots created. Use the #bot create command to create a bot."); - } - - if(!strcasecmp(sep->arg[1], "mana")) { - bool listAll = false; - Bot* bot = 0; - if(sep->argnum == 2) { - if(std::string(sep->arg[2]).compare("all") == 0) - listAll = true; - else { - std::string botName = std::string(sep->arg[2]); - Bot* tempBot = entity_list.GetBotByBotName(botName); - if(tempBot && tempBot->GetBotOwner() == c) - bot = tempBot; - } - } else { - if(c->GetTarget() && c->GetTarget()->IsBot()) - bot = c->GetTarget()->CastToBot(); - } - - if(bot && !listAll) { - if(bot->GetClass() != WARRIOR && bot->GetClass() != MONK && bot->GetClass() != BARD && bot->GetClass() != BERSERKER && bot->GetClass() != ROGUE) - c->Message(0, "Name: %s -- Class: %s -- Mana: %3.1f%%", bot->GetCleanName(), ClassIdToString(bot->GetClass()).c_str(), bot->GetManaRatio()); - } else { - std::list spawnedBots = entity_list.GetBotsByBotOwnerCharacterID(c->CharacterID()); - if(!spawnedBots.empty()) { - for(std::list::iterator botsListItr = spawnedBots.begin(); botsListItr != spawnedBots.end(); ++botsListItr) { - Bot* tempBot = *botsListItr; - if(tempBot) { - if(tempBot->GetClass() != WARRIOR && tempBot->GetClass() != MONK && tempBot->GetClass() != BARD && tempBot->GetClass() != BERSERKER && tempBot->GetClass() != ROGUE) - c->Message(0, "Name: %s -- Class: %s -- Mana: %3.1f%%", tempBot->GetCleanName(), ClassIdToString(tempBot->GetClass()).c_str(), tempBot->GetManaRatio()); - } - } - } - else - c->Message(0, "You have no spawned bots in this zone."); - } - return; - } - - if(!strcasecmp(sep->arg[1], "spawn") ) { - if (RuleB(Bots, BotCharacterLevelEnabled)) { - if (c->GetLevel() < RuleI(Bots, BotCharacterLevel)) { - c->Message(0, "You are only level %d, you must be level %d to spawn a bot!", c->GetLevel(), RuleI(Bots, BotCharacterLevel)); - return; - } - } - - uint32 botId = GetBotIDByBotName(std::string(sep->arg[2])); - if(GetBotOwnerCharacterID(botId, &TempErrorMessage) != c->CharacterID()) { - c->Message(0, "You can't spawn a bot that you don't own."); - return; - } - - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - return; - } - - if(c->GetFeigned()) { - c->Message(0, "You can't summon bots while you are feigned."); - return; - } - - if(c->IsGrouped()) { - Group *g = entity_list.GetGroupByClient(c); - for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(g && g->members[i] && !g->members[i]->qglobal && (g->members[i]->GetAppearance() != eaDead) - && (g->members[i]->IsEngaged() || (g->members[i]->IsClient() && g->members[i]->CastToClient()->GetAggroCount()))) { - c->Message(0, "You can't summon bots while you are engaged."); - return; - } - - if(g && g->members[i] && g->members[i]->qglobal) - return; - } - } else { - if(c->GetAggroCount() > 0) { - c->Message(0, "You can't spawn bots while you are engaged."); - return; - } - } - - Mob* TempBotMob = entity_list.GetMobByBotID(botId); - if(TempBotMob) { - c->Message(0, "This bot is already in the zone."); - return; - } - - int spawnedBotCount = SpawnedBotCount(c->CharacterID(), &TempErrorMessage); - - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - return; - } - - if(RuleB(Bots, QuestableSpawnLimit) && !c->GetGM()) { - const int allowedBots = AllowedBotSpawns(c->CharacterID(), &TempErrorMessage); - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - return; - } - - if(allowedBots == 0) { - c->Message(0, "You cannot spawn any bots."); - return; - } - - if(spawnedBotCount >= allowedBots) { - c->Message(0, "You cannot spawn more than %i bots.", spawnedBotCount); - return; - } - - } - - if(spawnedBotCount >= RuleI(Bots, SpawnLimit) && !c->GetGM()) { - c->Message(0, "You cannot spawn more than %i bots.", spawnedBotCount); - return; - } - - Bot* TempBot = LoadBot(botId, &TempErrorMessage); - - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - safe_delete(TempBot); - return; - } - - if(TempBot) { - TempBot->Spawn(c, &TempErrorMessage); - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - safe_delete(TempBot); - return; - } - - TempBot->BotGroupSay(TempBot, "I am ready for battle."); - } - else - c->Message(0, "BotID: %i not found", atoi(sep->arg[2])); - - return; - } - - if(!strcasecmp(sep->arg[1], "archery")) { - if((c->GetTarget() == nullptr) || (c->GetTarget() == c) || !c->GetTarget()->IsBot()) { - c->Message(15, "You must target a bot!"); - return; - } - - Bot* archerBot = c->GetTarget()->CastToBot(); - if(archerBot) { - if(archerBot->IsBotArcher()) - archerBot->SetBotArcher(false); - else - archerBot->SetBotArcher(true); - - archerBot->ChangeBotArcherWeapons(archerBot->IsBotArcher()); - if(archerBot->GetClass() == RANGER && archerBot->GetLevel() >= 61) - archerBot->SetRangerAutoWeaponSelect(archerBot->IsBotArcher()); - } - - return; - } - - if(!strcasecmp(sep->arg[1], "picklock")) { - if((c->GetTarget() == nullptr) || (c->GetTarget() == c) || !c->GetTarget()->IsBot() || (c->GetTarget()->GetClass() != ROGUE)) - c->Message(15, "You must target a Rogue bot!"); - else - entity_list.BotPickLock(c->GetTarget()->CastToBot()); - - return; - } - - if(!strcasecmp(sep->arg[1], "summon")) { - if((c->GetTarget() == nullptr) || (c->GetTarget() == c) || !c->GetTarget()->IsBot() || c->GetTarget()->IsPet()) - c->Message(15, "You must target a bot!"); - else if(c->GetTarget()->IsMob() && !c->GetTarget()->IsPet()) { - Mob *b = c->GetTarget(); - if(b) { - if(!b->IsBot()) - c->Message(15, "You must target a bot!"); - else if((b->CastToBot()->GetBotOwnerCharacterID() != c->CharacterID())) - b->CastToBot()->BotGroupSay(b->CastToBot(), "You can only summon your own bots."); - else { - b->SetTarget(c->CastToMob()); - b->Warp(glm::vec3(c->GetPosition())); - } - } - } - - return; - } - - if(!strcasecmp(sep->arg[1], "inventory") && !strcasecmp(sep->arg[2], "list")) { - if(c->GetTarget() != nullptr) { - if(c->GetTarget()->IsBot() && c->GetTarget()->CastToBot()->GetBotOwnerCharacterID() == c->CharacterID()) { - Mob* b = c->GetTarget(); - int x = c->GetTarget()->CastToBot()->GetBotItemsCount(&TempErrorMessage); - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - return; - } - - const char* equipped[EmuConstants::EQUIPMENT_SIZE + 1] = {"Charm", "Left Ear", "Head", "Face", "Right Ear", "Neck", "Shoulders", "Arms", "Back", - "Left Wrist", "Right Wrist", "Range", "Hands", "Primary Hand", "Secondary Hand", - "Left Finger", "Right Finger", "Chest", "Legs", "Feet", "Waist", "Ammo", "Powersource" }; - - const ItemInst* inst = nullptr; - const Item_Struct* item = nullptr; - bool is2Hweapon = false; - - std::string item_link; - Client::TextLink linker; - linker.SetLinkType(linker.linkItemInst); - - for(int i = EmuConstants::EQUIPMENT_BEGIN; i <= (EmuConstants::EQUIPMENT_END + 1); ++i) { - if((i == MainSecondary) && is2Hweapon) - continue; - - inst = b->CastToBot()->GetBotItem(i == 22 ? 9999 : i); - if (inst) - item = inst->GetItem(); - else - item = nullptr; - - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - return; - } - - if(item == nullptr) { - c->Message(15, "I need something for my %s (Item %i)", equipped[i], (i == 22 ? 9999 : i)); - continue; - } - - if((i == MainPrimary) && ((item->ItemType == ItemType2HSlash) || (item->ItemType == ItemType2HBlunt) || (item->ItemType == ItemType2HPiercing))) { - is2Hweapon = true; - } - - linker.SetItemInst(inst); - item_link = linker.GenerateLink(); - c->Message(15, "Using %s in my %s (Item %i)", item_link.c_str(), equipped[i], (i == 22 ? 9999 : i)); - } - } - else - c->Message(15, "You must group your bot first."); - } - else - c->Message(15, "You must target a bot first."); - - return; - } - - if(!strcasecmp(sep->arg[1], "inventory") && !strcasecmp(sep->arg[2], "remove")) { - if((c->GetTarget() == nullptr) || (sep->arg[3][0] == '\0') || !c->GetTarget()->IsBot()) { - c->Message(15, "Usage: #bot inventory remove [slotid] (You must have a bot targeted) "); - return; - } else if(c->GetTarget()->IsBot() && c->GetTarget()->CastToBot()->GetBotOwnerCharacterID() == c->CharacterID()) { - if(c->GetTradeskillObject() || (c->trade->state == Trading)) - return; - - int slotId = atoi(sep->arg[3]); - if((slotId > EmuConstants::EQUIPMENT_END || slotId < EmuConstants::EQUIPMENT_BEGIN) && slotId != 9999) { - c->Message(15, "A bot has 22 slots in its inventory, please choose a slot between 0 and 21 or 9999."); - return; - } - - const char* equipped[EmuConstants::EQUIPMENT_SIZE + 1] = {"Charm", "Left Ear", "Head", "Face", "Right Ear", "Neck", "Shoulders", "Arms", "Back", - "Left Wrist", "Right Wrist", "Range", "Hands", "Primary Hand", "Secondary Hand", - "Left Finger", "Right Finger", "Chest", "Legs", "Feet", "Waist", "Ammo", "Powersource" }; - - const Item_Struct* itm = nullptr; - const ItemInst* itminst = c->GetTarget()->CastToBot()->GetBotItem(slotId); - if(itminst) - itm = itminst->GetItem(); - - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - return; - } - - // Don't allow the player to remove a lore item they already possess and cause a crash - bool failedLoreCheck = false; - if(itminst) { - for (int m = AUG_BEGIN; m < EmuConstants::ITEM_COMMON_SIZE; ++m) { - ItemInst *itma = itminst->GetAugment(m); - if(itma) { - if(c->CheckLoreConflict(itma->GetItem())) - failedLoreCheck = true; - } - } - - if(c->CheckLoreConflict(itm)) - failedLoreCheck = true; - } - if(!failedLoreCheck) { - if(itm) { - c->PushItemOnCursor(*itminst, true); - Bot *gearbot = c->GetTarget()->CastToBot(); - if((slotId == MainRange)||(slotId == MainAmmo)||(slotId == MainPrimary)||(slotId == MainSecondary)) - gearbot->SetBotArcher(false); - - gearbot->RemoveBotItemBySlot(slotId, &TempErrorMessage); - - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - return; - } - - gearbot->BotRemoveEquipItem(slotId); - gearbot->CalcBotStats(); - switch(slotId) { - case MainCharm: - case MainEar1: - case MainHead: - case MainFace: - case MainEar2: - case MainNeck: - case MainBack: - case MainWrist1: - case MainWrist2: - case MainRange: - case MainPrimary: - case MainSecondary: - case MainFinger1: - case MainFinger2: - case MainChest: - case MainWaist: - case MainPowerSource: - case MainAmmo: - gearbot->BotGroupSay(gearbot, "My %s is now unequipped.", equipped[slotId]); - break; - case MainShoulders: - case MainArms: - case MainHands: - case MainLegs: - case MainFeet: - gearbot->BotGroupSay(gearbot, "My %s are now unequipped.", equipped[slotId]); - break; - default: - break; - } - } - else { - switch(slotId) { - case MainCharm: - case MainEar1: - case MainHead: - case MainFace: - case MainEar2: - case MainNeck: - case MainBack: - case MainWrist1: - case MainWrist2: - case MainRange: - case MainPrimary: - case MainSecondary: - case MainFinger1: - case MainFinger2: - case MainChest: - case MainWaist: - case MainPowerSource: - case MainAmmo: - c->GetTarget()->CastToBot()->BotGroupSay(c->GetTarget()->CastToBot(), "My %s is already unequipped.", equipped[slotId]); - break; - case MainShoulders: - case MainArms: - case MainHands: - case MainLegs: - case MainFeet: - c->GetTarget()->CastToBot()->BotGroupSay(c->GetTarget()->CastToBot(), "My %s are already unequipped.", equipped[slotId]); - break; - default: - break; - } - } - } - else { - c->Message_StringID(0, PICK_LORE); - } - } - return; - } - - if(!strcasecmp(sep->arg[1], "update")) { - if((c->GetTarget() != nullptr) && c->GetTarget()->IsBot()) { - if(c->GetLevel() <= c->GetTarget()->GetLevel()) { - c->Message(15, "This bot has already been updated."); - return; - } - - if(c->IsGrouped()) { - Group *g = entity_list.GetGroupByClient(c); - for (int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(g && g->members[i] && g->members[i]->IsEngaged()) { - c->Message(15, "You can't update bots while you are engaged."); - return; - } - } - } - - if((c->GetTarget()->CastToBot()->GetBotOwner() == c->CastToMob()) && !c->GetFeigned()) { - Bot* bot = c->GetTarget()->CastToBot(); - bot->SetPetChooser(false); - bot->CalcBotStats(); - } else { - if(c->GetFeigned()) - c->Message(15, "You cannot update bots while feigned."); - else - c->Message(15, "You must target your bot first."); - } - } - else - c->Message(15, "You must target a bot first."); - - return; - } - - //Bind - if(!strcasecmp(sep->arg[1], "bind")) { - Mob *binder = nullptr; - bool hasbinder = false; - if(c->IsGrouped()) { - Group *g = c->GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(g->members[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == CLERIC)) { - hasbinder = true; - binder = g->members[i]; - } - } - - if(!hasbinder) - c->Message(15, "You must have a Cleric in your group."); - } - } - - if(hasbinder) { - binder->CastToBot()->BotGroupSay(binder->CastToBot(), "Attempting to bind you %s.", c->GetName()); - binder->CastToNPC()->CastSpell(35, c->GetID(), 1, -1, -1); - } - return; - } - - // Rune - if(!strcasecmp(sep->arg[1], "rune")) { - Mob *runeer = nullptr; - bool hasruneer = false; - if(c->IsGrouped()) { - Group *g = c->GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(g->members[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == ENCHANTER)) { - hasruneer = true; - runeer = g->members[i]; - } - } - - if(!hasruneer) - c->Message(15, "You must have an Enchanter in your group."); - } - } - - if(hasruneer) { - if (c->GetLevel() <= 12) - runeer->CastToBot()->BotGroupSay(runeer->CastToBot(), "I need to be level 13 or higher for this..."); - else if ((c->GetLevel() >= 13) && (c->GetLevel() <= 21)) { - runeer->CastToBot()->BotGroupSay(runeer->CastToBot(), "Casting Rune I..."); - runeer->CastSpell(481, c->GetID(), 1, -1, -1); - } else if ((c->GetLevel() >= 22) && (c->GetLevel() <= 32)) { - runeer->CastToBot()->BotGroupSay(runeer->CastToBot(), "Casting Rune II..."); - runeer->CastSpell(482, c->GetID(), 1, -1, -1); - } else if ((c->GetLevel() >= 33) && (c->GetLevel() <= 39)) { - runeer->CastToBot()->BotGroupSay(runeer->CastToBot(), "Casting Rune III..."); - runeer->CastSpell(483, c->GetID(), 1, -1, -1); - } else if ((c->GetLevel() >= 40) && (c->GetLevel() <= 51)) { - runeer->CastToBot()->BotGroupSay(runeer->CastToBot(), "Casting Rune IV..."); - runeer->CastSpell(484, c->GetID(), 1, -1, -1); - } else if ((c->GetLevel() >= 52) && (c->GetLevel() <= 60)) { - runeer->CastToBot()->BotGroupSay(runeer->CastToBot(), "Casting Rune V..."); - runeer->CastSpell(1689, c->GetID(), 1, -1, -1); - } else if (c->GetLevel() >= 61){ - runeer->CastToBot()->BotGroupSay(runeer->CastToBot(), "Casting Rune of Zebuxoruk..."); - runeer->CastSpell(3343, c->GetID(), 1, -1, -1); - } - } - return; - } - - //Tracking - if(!strcasecmp(sep->arg[1], "track") && c->IsGrouped()) { - Mob *Tracker; - uint32 TrackerClass = 0; - Group *g = c->GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(g->members[i] && g->members[i]->IsBot()) { - switch(g->members[i]->GetClass()) { - case RANGER: - Tracker = g->members[i]; - TrackerClass = RANGER; - break; - case DRUID: - if(TrackerClass != RANGER) { - Tracker = g->members[i]; - TrackerClass = DRUID; - } - break; - case BARD: - if(TrackerClass == 0) { - Tracker = g->members[i]; - TrackerClass = BARD; - } - break; - default: - break; - } - } - } - - int Level = (c->GetLevel()); - int RangeR = (Level * 80); //Ranger - int RangeD = (Level * 30); //Druid - int RangeB = (Level * 20); //Bard - switch(TrackerClass) { - case RANGER: - if(!strcasecmp(sep->arg[2], "all")) { - Tracker->CastToBot()->BotGroupSay(Tracker->CastToBot(), "Tracking everything."); - entity_list.ShowSpawnWindow(c, RangeR, false); - } else if(!strcasecmp(sep->arg[2], "rare")) { - Tracker->CastToBot()->BotGroupSay(Tracker->CastToBot(), "Selective tracking."); - entity_list.ShowSpawnWindow(c, RangeR, true); - } else if(!strcasecmp(sep->arg[2], "near")) { - Tracker->CastToBot()->BotGroupSay(Tracker->CastToBot(), "Tracking mobs nearby."); - entity_list.ShowSpawnWindow(c, RangeD, false); - } else - Tracker->CastToBot()->BotGroupSay(Tracker->CastToBot(), "Do you want to %s, %s, or %s?", Tracker->CastToBot()->CreateSayLink(c, "#bot track all", "track all").c_str(), Tracker->CastToBot()->CreateSayLink(c, "#bot track near", "track near").c_str(), Tracker->CastToBot()->CreateSayLink(c, "#bot track rare", "track rare").c_str()); - - break; - case BARD: - if(TrackerClass != RANGER) - Tracker->CastToBot()->BotGroupSay(Tracker->CastToBot(), "Tracking up."); - entity_list.ShowSpawnWindow(c, RangeB, false); - break; - case DRUID: - if(TrackerClass = BARD) - Tracker->CastToBot()->BotGroupSay(Tracker->CastToBot(), "Tracking up."); - entity_list.ShowSpawnWindow(c, RangeD, false); - break; - default: - c->Message(15, "You must have a Ranger, Druid, or Bard in your group."); - break; - } - } - } - - //Cure - if ((!strcasecmp(sep->arg[1], "cure")) && (c->IsGrouped())) { - Mob *Curer; - uint32 CurerClass = 0; - Group *g = c->GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++){ - if(g->members[i] && g->members[i]->IsBot()) { - switch(g->members[i]->GetClass()) { - case CLERIC: - Curer = g->members[i]; - CurerClass = CLERIC; - break; - case SHAMAN: - if(CurerClass != CLERIC){ - Curer = g->members[i]; - CurerClass = SHAMAN; - } - break; - case DRUID: - if (CurerClass == 0){ - Curer = g->members[i]; - CurerClass = DRUID; - } - break; - default: - break; - } - } - } - switch(CurerClass) { - case CLERIC: - if (!strcasecmp(sep->arg[2], "poison") && (c->GetLevel() >= 1)) { - Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Trying to cure us of %s.", sep->arg[2]); - Curer->CastToBot()->Bot_Command_Cure(1, Curer->GetLevel()); - } else if (!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() >= 4)) { - Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Trying to cure us of %s.", sep->arg[2]); - Curer->CastToBot()->Bot_Command_Cure(2, Curer->GetLevel()); - } else if(!strcasecmp(sep->arg[2], "curse") && (c->GetLevel() >= 8)) { - Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Trying to cure us of %s.", sep->arg[2]); - Curer->CastToBot()->Bot_Command_Cure(3, Curer->GetLevel()); - } else if(!strcasecmp(sep->arg[2], "blindness") && (c->GetLevel() >= 3)) { - Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Trying to cure us of %s.", sep->arg[2]); - Curer->CastToBot()->Bot_Command_Cure(4, Curer->GetLevel()); - } else if (!strcasecmp(sep->arg[2], "curse") && (c->GetLevel() <= 8) - || !strcasecmp(sep->arg[2], "blindness") && (c->GetLevel() <= 3) - || !strcasecmp(sep->arg[2], "disease") && (c->GetLevel() <= 4) - || !strcasecmp(sep->arg[2], "poison") && (c->GetLevel() <= 1)) { - Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "I am not the required level yet."); - } else - Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Do you want to cure %s, %s, %s, or %s?", Curer->CastToBot()->CreateSayLink(c, "#bot cure poison", "poison").c_str(), Curer->CastToBot()->CreateSayLink(c, "#bot cure disease", "disease").c_str(), Curer->CastToBot()->CreateSayLink(c, "#bot cure curse", "curse").c_str(), Curer->CastToBot()->CreateSayLink(c, "#bot cure blindness", "blindness").c_str()); - - break; - case SHAMAN: - if (!strcasecmp(sep->arg[2], "poison") && (c->GetLevel() >= 2)) { - Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Trying to cure us of %s.", sep->arg[2]); - Curer->CastToBot()->Bot_Command_Cure(1, Curer->GetLevel()); - } else if (!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() >= 1)) { - Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Trying to cure us of %s.", sep->arg[2]); - Curer->CastToBot()->Bot_Command_Cure(2, Curer->GetLevel()); - } else if(!strcasecmp(sep->arg[2], "curse")) { - Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "I don't have that spell."); - } else if(!strcasecmp(sep->arg[2], "blindness") && (c->GetLevel() >= 7)) { - Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Trying to cure us of %s.", sep->arg[2]); - Curer->CastToBot()->Bot_Command_Cure(4, Curer->GetLevel()); - } else if (!strcasecmp(sep->arg[2], "blindness") && (c->GetLevel() <= 7) - || !strcasecmp(sep->arg[2], "disease") && (c->GetLevel() <= 1) - || !strcasecmp(sep->arg[2], "poison") && (c->GetLevel() <= 2)) { - Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "I am not the required level yet."); - } else - Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Do you want to cure %s, %s, or %s?", Curer->CastToBot()->CreateSayLink(c, "#bot cure poison", "poison").c_str(), Curer->CastToBot()->CreateSayLink(c, "#bot cure disease", "disease").c_str(), Curer->CastToBot()->CreateSayLink(c, "#bot cure blindness", "blindness").c_str()); - - break; - case DRUID: - if (!strcasecmp(sep->arg[2], "poison") && (c->GetLevel() >= 5)) { - Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Trying to cure us of %s.", sep->arg[2]); - Curer->CastToBot()->Bot_Command_Cure(1, Curer->GetLevel()); - } else if (!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() >= 4)) { - Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Trying to cure us of %s.", sep->arg[2]); - Curer->CastToBot()->Bot_Command_Cure(2, Curer->GetLevel()); - } else if(!strcasecmp(sep->arg[2], "curse")) { // Fire level 1 - Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "I don't have that spell."); - } else if(!strcasecmp(sep->arg[2], "blindness") && (c->GetLevel() >= 13)) { - Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "I don't have that spell."); - } else if (!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() <= 4) - || !strcasecmp(sep->arg[2], "poison") && (c->GetLevel() <= 5)) { - Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "I am not the required level yet."); - } else - Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Do you want to cure %s or %s?", Curer->CastToBot()->CreateSayLink(c, "#bot cure poison", "poison").c_str(), Curer->CastToBot()->CreateSayLink(c, "#bot cure disease", "disease").c_str()); - break; - default: - c->Message(15, "You must have a Cleric, Shaman, or Druid in your group."); - break; - } - } - } - - //Mez - if(!strcasecmp(sep->arg[1], "mez")) { - Mob *target = c->GetTarget(); - if(target == nullptr || target == c || target->IsBot() || (target->IsPet() && target->GetOwner() && target->GetOwner()->IsBot())) { - c->Message(15, "You must select a monster"); - return; - } - - if(c->IsGrouped()) { - bool hasmezzer = false; - Group *g = c->GetGroup(); - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(g && g->members[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == ENCHANTER)) { - hasmezzer = true; - Mob *mezzer = g->members[i]; - mezzer->CastToBot()->BotGroupSay(mezzer->CastToBot(), "Trying to mesmerize %s.", target->GetCleanName()); - mezzer->CastToBot()->MesmerizeTarget(target); - } - } - - if(!hasmezzer) - c->Message(15, "You must have an Enchanter in your group."); - } - return; - } - - //Lore (Identify item) - if(!strcasecmp(sep->arg[1], "lore")) { - if(c->IsGrouped()) { - bool hascaster = false; - Group *g = c->GetGroup(); - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(g && g->members[i] && g->members[i]->IsBot()) { - uint8 casterlevel = g->members[i]->GetLevel(); - switch(g->members[i]->GetClass()) { - case ENCHANTER: - if(casterlevel >= 15) - hascaster = true; - - break; - case WIZARD: - if(casterlevel >= 14) - hascaster = true; - - break; - case NECROMANCER: - if(casterlevel >= 17) - hascaster = true; - - break; - case MAGICIAN: - if(casterlevel >= 13) - hascaster = true; - - break; - default: - break; - } - if(hascaster) { - g->members[i]->CastToBot()->BotGroupSay(g->members[i]->CastToBot(), "Trying to Identify your item..."); - g->members[i]->CastSpell(305, c->GetID(), 1, -1, -1); - break; - } - } - } - - if(!hascaster) - c->Message(15, "You don't see anyone in your group that can cast Identify."); - } else - c->Message(15, "You don't see anyone in your group that can cast Identify."); - - return; - } - - //Resurrect - if(!strcasecmp(sep->arg[1], "resurrect")) { - Mob *target = c->GetTarget(); - if(target == nullptr || !target->IsPlayerCorpse()) { - c->Message(15, "You must select a corpse!"); - return; - } - - if(c->IsGrouped()) { - bool hasrezzer = false; - Group *g = c->GetGroup(); - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(g && g->members[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == CLERIC)) { - hasrezzer = true; - Mob *rezzer = g->members[i]; - rezzer->CastToBot()->BotGroupSay(rezzer->CastToBot(), "Trying to resurrect %s.", target->GetCleanName()); - rezzer->CastToBot()->Bot_Command_RezzTarget(target); - break; - } - } - - if(!hasrezzer) - c->Message(15, "You must have a Cleric in your group!"); - } else - c->Message(15, "You must have a Cleric in your group!"); - - return; - } - - if(!strcasecmp(sep->arg[1], "setpet")) { - if(c->GetTarget() && c->GetTarget()->IsBot() && (c->GetTarget()->GetClass() == MAGICIAN)) { - if(c->GetTarget()->CastToBot()->GetBotOwnerCharacterID() == c->CharacterID()) { - int botlevel = c->GetTarget()->GetLevel(); - c->GetTarget()->CastToBot()->SetPetChooser(true); - if(botlevel == 1) { - c->GetTarget()->CastToBot()->BotGroupSay(c->GetTarget()->CastToBot(), "I don't have any pets yet."); - return; - } - - if(!strcasecmp(sep->arg[2], "water")) { - c->GetTarget()->CastToBot()->SetPetChooserID(0); - } else if(!strcasecmp(sep->arg[2], "fire")) { - if(botlevel < 3) { - c->GetTarget()->CastToBot()->BotGroupSay(c->GetTarget()->CastToBot(), "I don't have that pet yet."); - return; - } else - c->GetTarget()->CastToBot()->SetPetChooserID(1); - } else if(!strcasecmp(sep->arg[2], "air")) { - if(botlevel < 4) { - c->GetTarget()->CastToBot()->BotGroupSay(c->GetTarget()->CastToBot(), "I don't have that pet yet."); - return; - } else - c->GetTarget()->CastToBot()->SetPetChooserID(2); - } else if(!strcasecmp(sep->arg[2], "earth")) { - if(botlevel < 5) { - c->GetTarget()->CastToBot()->BotGroupSay(c->GetTarget()->CastToBot(), "I don't have that pet yet."); - return; - } else - c->GetTarget()->CastToBot()->SetPetChooserID(3); - } else if(!strcasecmp(sep->arg[2], "monster")) { - if(botlevel < 30) { - c->GetTarget()->CastToBot()->BotGroupSay(c->GetTarget()->CastToBot(), "I don't have that pet yet."); - return; - } else - c->GetTarget()->CastToBot()->SetPetChooserID(4); - } - - if(c->GetTarget()->GetPet()) { - uint16 id = c->GetTarget()->GetPetID(); - c->GetTarget()->SetPetID(0); - c->GetTarget()->CastSpell(331, id); - } - } - } else - c->Message(15, "You must target your Magician bot!"); - - return; - } - - //Summon Corpse - if(!strcasecmp(sep->arg[1], "corpse") && !strcasecmp(sep->arg[2], "summon")) { - if(c->GetTarget() == nullptr) { - c->Message(15, "You must select player with his corpse in the zone!"); - return; - } - - if(c->IsGrouped()) { - bool hassummoner = false; - Mob *t = c->GetTarget(); - Group *g = c->GetGroup(); - int summonerlevel = 0; - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(g && g->members[i] && g->members[i]->IsBot() && ((g->members[i]->GetClass() == NECROMANCER)||(g->members[i]->GetClass() == SHADOWKNIGHT))) { - hassummoner = true; - summonerlevel = g->members[i]->GetLevel(); - g->members[i]->InterruptSpell(); - if(!t->IsClient()) { - g->members[i]->CastToBot()->BotGroupSay(g->members[i]->CastToBot(), "You have to target a player with a corpse in the zone!"); - return; - } else { - g->members[i]->SetTarget(t); - if(summonerlevel < 12) { - g->members[i]->CastToBot()->BotGroupSay(g->members[i]->CastToBot(), "I don't have that spell yet."); - } else if((summonerlevel > 11) && (summonerlevel < 35)) { - g->members[i]->CastToBot()->BotGroupSay(g->members[i]->CastToBot(), "Attempting to summon %s\'s corpse.", t->GetCleanName()); - g->members[i]->CastSpell(2213, t->GetID(), 1, -1, -1); - return; - } else if((summonerlevel > 34) && (summonerlevel < 71)) { - g->members[i]->CastToBot()->BotGroupSay(g->members[i]->CastToBot(), "Attempting to summon %s\'s corpse.", t->GetCleanName()); - g->members[i]->CastSpell(3, t->GetID(), 1, -1, -1); - return; - } else if(summonerlevel > 70) { - g->members[i]->CastToBot()->BotGroupSay(g->members[i]->CastToBot(), "Attempting to summon %s\'s corpse.", t->GetCleanName()); - g->members[i]->CastSpell(10042, t->GetID(), 1, -1, -1); - return; - } - } - } - } - - if (!hassummoner) - c->Message(15, "You must have a Necromancer or Shadow Knight in your group."); - - return; - } - } - - //Pacify - if(!strcasecmp(sep->arg[1], "target") && !strcasecmp(sep->arg[2], "calm")) { - Mob *target = c->GetTarget(); - if(target == nullptr || target->IsClient() || target->IsBot() || (target->IsPet() && target->GetOwner() && target->GetOwner()->IsBot())) - c->Message(15, "You must select a monster!"); - else { - if(c->IsGrouped()) { - Group *g = c->GetGroup(); - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - // seperated cleric and chanter so chanter is primary - if(g && g->members[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == ENCHANTER)) { - Bot *pacer = g->members[i]->CastToBot(); - pacer->BotGroupSay(pacer, "Trying to pacify %s.", target->GetCleanName()); - if(pacer->Bot_Command_CalmTarget(target)) { - if(target->FindType(SE_Lull) || target->FindType(SE_Harmony) || target->FindType(SE_InstantHate)) - c->Message(0, "I have successfully pacified %s.", target->GetCleanName()); - - return; - } - else - c->Message(0, "I failed to pacify %s.", target->GetCleanName()); - } - // seperated cleric and chanter so chanter is primary - if(g && g->members[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == CLERIC) && (GroupHasEnchanterClass(g) == false)) { - Bot *pacer = g->members[i]->CastToBot(); - pacer->BotGroupSay(pacer, "Trying to pacify %s.", target->GetCleanName()); - - if(pacer->Bot_Command_CalmTarget(target)) { - if(target->FindType(SE_Lull) || target->FindType(SE_Harmony) || target->FindType(SE_InstantHate)) - c->Message(0, "I have successfully pacified %s.", target->GetCleanName()); - - return; - } - else - c->Message(0, "I failed to pacify %s.", target->GetCleanName()); - } - } - } - } - - return; - } - - //Charm - if(!strcasecmp(sep->arg[1], "charm")) { - Mob *target = c->GetTarget(); - if(target == nullptr || target->IsClient() || target->IsBot() || (target->IsPet() && target->GetOwner() && target->GetOwner()->IsBot())) { - c->Message(15, "You must select a monster!"); - return; - } - - uint32 DBtype = c->GetTarget()->GetBodyType(); - Mob *Charmer; - uint32 CharmerClass = 0; - Group *g = c->GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++){ - if(g->members[i] && g->members[i]->IsBot()) { - switch(g->members[i]->GetClass()) { - case ENCHANTER: - Charmer = g->members[i]; - CharmerClass = ENCHANTER; - break; - case NECROMANCER: - if(CharmerClass != ENCHANTER){ - Charmer = g->members[i]; - CharmerClass = NECROMANCER; - } - break; - case DRUID: - if (CharmerClass == 0){ - Charmer = g->members[i]; - CharmerClass = DRUID; - } - break; - default: - break; - } - } - } - switch(CharmerClass) { - case ENCHANTER: - if (c->GetLevel() >= 11) { - Charmer->CastToBot()->BotGroupSay(Charmer->CastToBot(), "Trying to charm %s.", target->GetCleanName()); - Charmer->CastToBot()->Bot_Command_CharmTarget(1, target); - } - else if (c->GetLevel() <= 10) - Charmer->CastToBot()->BotGroupSay(Charmer->CastToBot(), "I am not the required level yet."); - else - Charmer->CastToBot()->BotGroupSay(Charmer->CastToBot(), "Mob level is too high or can't be charmed."); - break; - case NECROMANCER: - if ((c->GetLevel() >= 18) && (DBtype == 3)) { - Charmer->CastToBot()->BotGroupSay(Charmer->CastToBot(), "Trying to charm %s.", target->GetCleanName()); - Charmer->CastToBot()->Bot_Command_CharmTarget(2, target); - } - else if (c->GetLevel() <= 17) - Charmer->CastToBot()->BotGroupSay(Charmer->CastToBot(), "I am not the required level yet."); - else - Charmer->CastToBot()->BotGroupSay(Charmer->CastToBot(), "Mob is not undead."); - break; - case DRUID: - if ((c->GetLevel() >= 13) && (DBtype == 21)) { - Charmer->CastToBot()->BotGroupSay(Charmer->CastToBot(), "Trying to charm %s.", target->GetCleanName()); - Charmer->CastToBot()->Bot_Command_CharmTarget(3, target); - } - else if (c->GetLevel() <= 12) - Charmer->CastToBot()->BotGroupSay(Charmer->CastToBot(), "I am not the required level yet."); - else - Charmer->CastToBot()->BotGroupSay(Charmer->CastToBot(), "Mob is not an animal."); - break; - default: - c->Message(15, "You must have an Enchanter, Necromancer, or Druid in your group."); - break; - } - } - } - - // Remove Bot's Pet - if(!strcasecmp(sep->arg[1], "pet") && !strcasecmp(sep->arg[2], "remove")) { - if(c->GetTarget() != nullptr) { - if (c->IsGrouped() && c->GetTarget()->IsBot() && (c->GetTarget()->CastToBot()->GetBotOwner() == c) && - ((c->GetTarget()->GetClass() == NECROMANCER) || (c->GetTarget()->GetClass() == ENCHANTER) || (c->GetTarget()->GetClass() == DRUID))) { - if(c->GetTarget()->CastToBot()->IsBotCharmer()) { - c->GetTarget()->CastToBot()->SetBotCharmer(false); - c->GetTarget()->CastToBot()->BotGroupSay(c->GetTarget()->CastToBot(), "Using a summoned pet."); - } else { - if(c->GetTarget()->GetPet()) { - c->GetTarget()->GetPet()->Say_StringID(PET_GETLOST_STRING); - c->GetTarget()->GetPet()->Depop(false); - c->GetTarget()->SetPetID(0); - } - c->GetTarget()->CastToBot()->SetBotCharmer(true); - c->GetTarget()->CastToBot()->BotGroupSay(c->GetTarget()->CastToBot(), "Available for Dire Charm command."); - } - } - else - c->Message(15, "You must target your Enchanter, Necromancer, or Druid bot."); - } - else - c->Message(15, "You must target an Enchanter, Necromancer, or Druid bot."); - - return; - } - - //Dire Charm - if(!strcasecmp(sep->arg[1], "Dire") && !strcasecmp(sep->arg[2], "Charm")) { - Mob *target = c->GetTarget(); - if(target == nullptr || target->IsClient() || target->IsBot() || (target->IsPet() && target->GetOwner() && target->GetOwner()->IsBot())) { - c->Message(15, "You must select a monster"); - return; - } - uint32 DBtype = c->GetTarget()->GetBodyType(); - Mob *Direr; - uint32 DirerClass = 0; - Group *g = c->GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++){ - if(g->members[i] && g->members[i]->IsBot()) { - switch(g->members[i]->GetClass()) { - case ENCHANTER: - Direr = g->members[i]; - DirerClass = ENCHANTER; - break; - case NECROMANCER: - if(DirerClass != ENCHANTER){ - Direr = g->members[i]; - DirerClass = NECROMANCER; - } - break; - case DRUID: - if (DirerClass == 0){ - Direr = g->members[i]; - DirerClass = DRUID; - } - break; - default: - break; - } - } - } - switch(DirerClass) { - case ENCHANTER: - if (c->GetLevel() >= 55) { - Direr->CastToBot()->BotGroupSay(Direr->CastToBot(), "Trying to dire charm %s.", target->GetCleanName()); - Direr->CastToBot()->Bot_Command_DireTarget (1,target); - } - else if (c->GetLevel() <= 55) - Direr->CastToBot()->BotGroupSay(Direr->CastToBot(), "I am not the required level yet."); - else - Direr->CastToBot()->BotGroupSay(Direr->CastToBot(), "Mob level is too high or can't be charmed."); - break; - case NECROMANCER: - if ((c->GetLevel() >= 55) && (DBtype == 3)) { - Direr->CastToBot()->BotGroupSay(Direr->CastToBot(), "Trying to dire charm %s.", target->GetCleanName()); - Direr->CastToBot()->Bot_Command_DireTarget (2,target); - } - else if (c->GetLevel() <= 55) - Direr->CastToBot()->BotGroupSay(Direr->CastToBot(), "I am not the required level yet."); - else - Direr->CastToBot()->BotGroupSay(Direr->CastToBot(), "Mob is not undead."); - break; - case DRUID: - if ((c->GetLevel() >= 55) && (DBtype == 21)) { - Direr->CastToBot()->BotGroupSay(Direr->CastToBot(), "Trying to dire charm %s.", target->GetCleanName()); - Direr->CastToBot()->Bot_Command_DireTarget (3,target); - } - else if (c->GetLevel() <= 55) - Direr->CastToBot()->BotGroupSay(Direr->CastToBot(), "I am not the required level yet."); - else - Direr->CastToBot()->BotGroupSay(Direr->CastToBot(), "Mob is not an animal."); - break; - default: - c->Message(15, "You must have an Enchanter, Necromancer, or Druid in your group."); - break; - } - } - } - - // Evacuate - if(!strcasecmp(sep->arg[1], "evac")) { - Mob *evac = nullptr; - bool hasevac = false; - if(c->IsGrouped()) { - Group *g = c->GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if((g->members[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == DRUID)) || (g->members[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == WIZARD))) { - hasevac = true; - evac = g->members[i]; - } - } - - if(!hasevac) - c->Message(15, "You must have a Druid in your group."); - } - } - - if((hasevac) && (c->GetLevel() >= 18)) { - evac->CastToBot()->BotGroupSay(evac->CastToBot(), "Attempting to evacuate you, %s.", c->GetName()); - evac->CastToClient()->CastSpell(2183, c->GetID(), 1, -1, -1); - } - else if((hasevac) && (c->GetLevel() <= 17)) - evac->CastToBot()->BotGroupSay(evac->CastToBot(), "I'm not level 18 yet."); - - return; - } - - // Sow - if ((!strcasecmp(sep->arg[1], "speed")) && (c->IsGrouped())) { - Mob *Sower; - uint32 SowerClass = 0; - Group *g = c->GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++){ - if(g->members[i] && g->members[i]->IsBot()) { - switch(g->members[i]->GetClass()) { - case DRUID: - Sower = g->members[i]; - SowerClass = DRUID; - break; - case SHAMAN: - if (SowerClass != DRUID){ - Sower = g->members[i]; - SowerClass = SHAMAN; - } - break; - case RANGER: - if (SowerClass == 0){ - Sower = g->members[i]; - SowerClass = RANGER; - } - break; - case BEASTLORD: - if (SowerClass == 0){ - Sower = g->members[i]; - SowerClass = BEASTLORD; - } - break; - default: - break; - } - } - } - switch(SowerClass) { - case DRUID: - if ((!strcasecmp(sep->arg[2], "regular")) && (zone->CanCastOutdoor()) && (c->GetLevel() >= 10)) { - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "Casting Spirit of Wolf."); - Sower->CastSpell(278, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "regular")) && (zone->CanCastOutdoor()) && (c->GetLevel() <= 10)) - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I'm not level 10 yet."); - else if ((!strcasecmp(sep->arg[2], "wolf")) && zone->CanCastOutdoor() && (c->GetLevel() >= 20)) { - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "Casting group Spirit of Wolf."); - Sower->CastSpell(428, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "wolf")) && (c->GetLevel() <= 20)) - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I'm not level 20 yet."); - else if ((!strcasecmp(sep->arg[2], "feral")) && (c->GetLevel() >= 50)) { - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "Casting Feral Pack."); - Sower->CastSpell(4058, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "feral")) && (c->GetLevel() <= 50)) - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I'm not level 50 yet."); - else if ((!strcasecmp(sep->arg[2], "shrew")) && (c->GetLevel() >= 35)) { - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "Casting Pack Shrew."); - Sower->CastSpell(4055, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "wolf")) && (c->GetLevel() <= 35)) - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I'm not level 35 yet."); - else if ((!zone->CanCastOutdoor()) && (!strcasecmp(sep->arg[2], "regular")) || (!zone->CanCastOutdoor()) && (!strcasecmp(sep->arg[2], "wolf"))) - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I can't cast this spell indoors, try %s if you're 35 or higher, or %s if you're 50 or higher.", Sower->CastToBot()->CreateSayLink(c, "#bot speed shrew", "Pack Shrew").c_str(), Sower->CastToBot()->CreateSayLink(c, "#bot speed feral", "Feral Pack").c_str()); - else if (!zone->CanCastOutdoor()) - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I can't cast this spell indoors, try 5s if you're 35 or higher, or %s if you're 50 or higher.", Sower->CastToBot()->CreateSayLink(c, "#bot speed shrew", "Pack Shrew").c_str(), Sower->CastToBot()->CreateSayLink(c, "#bot speed feral", "Feral Pack").c_str()); - else if (zone->CanCastOutdoor()) - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "Do you want %s or %s?", Sower->CastToBot()->CreateSayLink(c, "#bot speed regular", "Spirit of Wolf").c_str(), Sower->CastToBot()->CreateSayLink(c, "#bot speed wolf", "Group Spirit of Wolf").c_str()); - else if (!zone->CanCastOutdoor()) - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I can't cast this spell indoors, try %s if you're 35 or higher, or %s if you're 50 or higher.", Sower->CastToBot()->CreateSayLink(c, "#bot speed shrew", "Pack Shrew").c_str(), Sower->CastToBot()->CreateSayLink(c, "#bot speed feral", "Feral").c_str()); - break; - case SHAMAN: - if ((zone->CanCastOutdoor()) && (c->GetLevel() >= 9)) { - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "Casting Spirit of Wolf."); - Sower->CastToClient()->CastSpell(278, c->GetID(), 1, -1, -1); - } - else if (!zone->CanCastOutdoor()) - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I can't cast this spell indoors."); - else if (c->GetLevel() <= 9) - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I'm not level 9 yet."); - break; - case RANGER: - if ((zone->CanCastOutdoor()) && (c->GetLevel() >= 28)){ - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "Casting Spirit of Wolf."); - Sower->CastToClient()->CastSpell(278, c->GetID(), 1, -1, -1); - } - else if (!zone->CanCastOutdoor()) - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I can't cast this spell indoors."); - else if (c->GetLevel() <= 28) - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I'm not level 28 yet."); - break; - case BEASTLORD: - if((zone->CanCastOutdoor()) && (c->GetLevel() >= 24)) { - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "Casting Spirit of Wolf."); - Sower->CastToClient()->CastSpell(278, c->GetID(), 1, -1, -1); - } - else if (!zone->CanCastOutdoor()) - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I can't cast this spell indoors."); - else if (c->GetLevel() <= 24) - Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I'm not level 24 yet."); - break; - default: - c->Message(15, "You must have a Druid, Shaman, Ranger, or Beastlord in your group."); - break; - } - } - } - - // Shrink - if ((!strcasecmp(sep->arg[1], "shrink")) && (c->IsGrouped())) { - Mob *Shrinker; - uint32 ShrinkerClass = 0; - Group *g = c->GetGroup(); - Mob *target = c->GetTarget(); - if(target == nullptr || (!target->IsClient() && (c->GetTarget()->CastToBot()->GetBotOwner() != c))) - c->Message(15, "You must select a player or bot you own!"); - - else if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++){ - if(g->members[i] && g->members[i]->IsBot()) { - switch(g->members[i]->GetClass()) { - case SHAMAN: - Shrinker = g->members[i]; - ShrinkerClass = SHAMAN; - break; - case BEASTLORD: - if (ShrinkerClass != SHAMAN){ - Shrinker = g->members[i]; - ShrinkerClass = BEASTLORD; - } - break; - default: - break; - } - } - } - switch(ShrinkerClass) { - case SHAMAN: - if (c->GetLevel() >= 15) { - Shrinker->CastToBot()->BotGroupSay(Shrinker->CastToBot(), "Casting Shrink."); - Shrinker->CastToBot()->SpellOnTarget(345, target); - } - else if (c->GetLevel() <= 14) - Shrinker->CastToBot()->BotGroupSay(Shrinker->CastToBot(), "I'm not level 15 yet."); - break; - case BEASTLORD: - if (c->GetLevel() >= 23) { - Shrinker->CastToBot()->BotGroupSay(Shrinker->CastToBot(), "Casting Shrink."); - Shrinker->CastToBot()->SpellOnTarget(345, target); - } - else if (c->GetLevel() <= 22) - Shrinker->CastToBot()->BotGroupSay(Shrinker->CastToBot(), "I'm not level 23 yet."); - break; - default: - c->Message(15, "You must have a Shaman or Beastlord in your group."); - break; - } - } - } - - // Gate - if ((!strcasecmp(sep->arg[1], "gate")) && (c->IsGrouped())) { - const char* druidgate[25][4] = { { "#bot gate karana", "karana", "North Karana", "550" }, { "#bot gate commons", "commons", "West Commonlands", "551" }, - { "#bot gate tox", "tox", "Toxxulia Forest", "552" }, { "#bot gate butcher", "butcher", "Butcherblock Mountains", "553" }, { "#bot gate lava", "lava", "Lavastorm Mountains", "554" }, - { "#bot gate ro", "ro", "South Ro", "555" }, { "#bot gate feerott", "feerrott", "Feerrott", "556" }, { "#bot gate steamfont", "steamfont", "Steamfont Mountains", "557" }, - { "#bot gate misty", "misty", "Misty Thicket", "558" }, { "#bot gate wakening", "wakening", "Wakening Lands", "1398" }, { "#bot gate iceclad", "iceclad", "Ieclad Ocean", "1434" }, - { "#bot gate divide", "divide", "The Great Divide", "1438" }, { "#bot gate cobalt", "cobalt", "Cobalt Scar", "1440" }, { "#bot gate combines", "combines", "The Combines", "1517" }, - { "#bot gate surefall", "surefall", "Surefall Glade", "2020" }, { "#bot gate grimling", "grimling", "Grimling Forest", "2419" }, { "#bot gate twilight", "twilight", "The Twilight Sea", "2424" }, - { "#bot gate dawnshroud", "dawnshroud", "Dawnshroud Peak", "2429" }, { "#bot gate nexus", "nexus", "The Nexus", "2432" }, { "#bot gate pok", "pok", "Plane of Knowledge", "3184" }, - { "#bot gate stonebrunt", "stonebrunt", "Stonebrunt Mountains", "3792" }, { "#bot gate bloodfields", "bloodfields", "Bloodfields", "6184" }, - { "#bot gate emerald", "emerald", "The Emerald Jungle", "1737" }, { "#bot gate skyfire", "skyfire", "Skyfire Mountains", "1736" }, { "#bot gate slaughter", "slaughter", "Wall of Slaughter", "6179" } }; - const char* wizardgate[24][4] = { { "#bot gate commons", "commons", "West Commonlands", "566" }, { "#bot gate fay", "fay", "Greater Faydark", "563" }, - { "#bot gate ro", "ro", "South Ro", "567" }, { "#bot gate tox", "tox", "Toxxulia Forest", "561" }, { "#bot gate nk", "nk", "North Karana", "562" }, - { "#bot gate nek", "nek", "Nektulos Forest", "564" }, { "#bot gate wakening", "wakening", "Wakening Lands", "1399" }, { "#bot gate iceclad", "iceclad", "Iceclad Ocean", "1418" }, - { "#bot gate divide", "divide", "The Great Divide", "1423" }, { "#bot gate cobalt", "cobalt", "Cobaltscar", "1425" }, { "#bot gate dreadlands", "dreadlands", "Dreadlands", "1516" }, - { "#bot gate wk", "wk", "West Karana", "568" }, { "#bot gate twilight", "twilight", "Twilight Sea", "2425" }, { "#bot gate dawnshroud", "dawnshroud", "Dawnshroud Peak", "2430" }, - { "#bot gate nexus", "nexus", "Nexus", "2944" }, { "#bot gate pok", "pok", "Plane of Knowledge", "3180" }, { "#bot gate wos", "wos", "Wall of Slaughter", "6178" }, - { "#bot gate grimling", "grimling", "Grimling Forest", "2420" }, { "#bot gate emerald", "emerald", "Emerald Jungle", "1739" }, { "#bot gate hateplane", "hateplane", "Plane of Hate", "666" }, - { "#bot gate airplane", "airplane", "Plane of Sky", "674" }, { "#bot gate skyfire", "skyfire", "Skyfire Mountains", "1738" }, - { "#bot gate bloodfields", "bloodfields", "Bloodfields", "6183" }, { "#bot gate stonebrunt", "stonebrunt", "Stonebrunt Mountains", "3793" } }; - bool valid = false; - Mob *Gater; - uint32 GaterClass = 0; - Group *g = c->GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++){ - if(g->members[i] && g->members[i]->IsBot()) { - switch(g->members[i]->GetClass()) { - case DRUID: - Gater = g->members[i]; - GaterClass = DRUID; - break; - case WIZARD: - if (GaterClass == 0){ - Gater = g->members[i]; - GaterClass = WIZARD; - } - break; - default: - break; - } - } - } - switch(GaterClass) { - case DRUID: { - for (int i = 0; i < 25; i++) { - if (!strcasecmp(sep->arg[2], druidgate[i][1])) { - Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Gating to %s!", druidgate[i][2]); - Gater->CastSpell(atoi(druidgate[i][3]), c->GetID(), 1, -1, -1); - valid = true; - } - } - if (!valid) { - Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "With the proper level I can gate you to all of the following zones:"); - for (int i = 0; i < 25; i++) - Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "%s", Gater->CastToBot()->CreateSayLink(c, druidgate[i][0], druidgate[i][2]).c_str()); - } - break; - } - case WIZARD: { - for (int i = 0; i < 24; i++) { - if (!strcasecmp(sep->arg[2], wizardgate[i][1])) { - Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Gating to %s!", wizardgate[i][2]); - Gater->CastSpell(atoi(wizardgate[i][3]), c->GetID(), 1, -1, -1); - valid = true; - } - } - if (!valid) { - Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "With the proper level I can gate you to all of the following zones:"); - for (int i = 0; i < 24; i++) - Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "%s", Gater->CastToBot()->CreateSayLink(c, wizardgate[i][0], wizardgate[i][2]).c_str()); - } - break; - } - default: - c->Message(15, "You must have a Druid or Wizard in your group."); - break; - } - } - } - - //Endure Breath - if ((!strcasecmp(sep->arg[1], "endureb")) && (c->IsGrouped())) { - Mob *Endurer; - uint32 EndurerClass = 0; - Group *g = c->GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++){ - if(g->members[i] && g->members[i]->IsBot()) { - switch(g->members[i]->GetClass()) { - case DRUID: - Endurer = g->members[i]; - EndurerClass = DRUID; - break; - case SHAMAN: - if (EndurerClass != DRUID){ - Endurer = g->members[i]; - EndurerClass = SHAMAN; - } - break; - case ENCHANTER: - if(EndurerClass == 0){ - Endurer = g->members[i]; - EndurerClass = ENCHANTER; - } - break; - case RANGER: - if(EndurerClass == 0) { - Endurer = g->members[i]; - EndurerClass = RANGER; - } - break; - case BEASTLORD: - if(EndurerClass == 0) { - Endurer = g->members[i]; - EndurerClass = BEASTLORD; - } - break; - default: - break; - } - } - } - switch(EndurerClass) { - case DRUID: - if (c->GetLevel() < 6) - Endurer->CastToBot()->BotGroupSay(Endurer->CastToBot(), "I'm not level 6 yet."); - else { - Endurer->CastToBot()->BotGroupSay(Endurer->CastToBot(), "Casting Enduring Breath."); - Endurer->CastSpell(86, c->GetID(), 1, -1, -1); - break; - } - break; - case SHAMAN: - if (c->GetLevel() < 12) - Endurer->CastToBot()->BotGroupSay(Endurer->CastToBot(), "I'm not level 12 yet."); - else { - Endurer->CastToBot()->BotGroupSay(Endurer->CastToBot(), "Casting Enduring Breath."); - Endurer->CastSpell(86, c->GetID(), 1, -1, -1); - } - break; - case RANGER: - if (c->GetLevel() < 20) - Endurer->CastToBot()->BotGroupSay(Endurer->CastToBot(), "I'm not level 20 yet."); - else { - Endurer->CastToBot()->BotGroupSay(Endurer->CastToBot(), "Casting Enduring Breath."); - Endurer->CastSpell(86, c->GetID(), 1, -1, -1); - } - break; - case ENCHANTER: - if (c->GetLevel() < 12) - Endurer->CastToBot()->BotGroupSay(Endurer->CastToBot(), "I'm not level 12 yet."); - else { - Endurer->CastToBot()->BotGroupSay(Endurer->CastToBot(), "Casting Enduring Breath."); - Endurer->CastSpell(86, c->GetID(), 1, -1, -1); - } - break; - case BEASTLORD: - if (c->GetLevel() < 25) - Endurer->CastToBot()->BotGroupSay(Endurer->CastToBot(), "I'm not level 25 yet."); - else { - Endurer->CastToBot()->BotGroupSay(Endurer->CastToBot(), "Casting Enduring Breath."); - Endurer->CastSpell(86, c->GetID(), 1, -1, -1); - } - break; - default: - c->Message(15, "You must have a Druid, Shaman, Ranger, Enchanter, or Beastlord in your group."); - break; - } - } - } - - //Invisible - if ((!strcasecmp(sep->arg[1], "invis")) && (c->IsGrouped())) { - Mob *Inviser; - uint32 InviserClass = 0; - Group *g = c->GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++){ - if(g->members[i] && g->members[i]->IsBot()) { - switch(g->members[i]->GetClass()) { - case ENCHANTER: - Inviser = g->members[i]; - InviserClass = ENCHANTER; - break; - case MAGICIAN: - if (InviserClass != ENCHANTER){ - Inviser = g->members[i]; - InviserClass = MAGICIAN; - } - break; - case WIZARD: - if((InviserClass != ENCHANTER) && (InviserClass != MAGICIAN)){ - Inviser = g->members[i]; - InviserClass = WIZARD; - } - break; - case NECROMANCER: - if(InviserClass == 0){ - Inviser = g->members[i]; - InviserClass = NECROMANCER; - } - break; - case DRUID: - if((InviserClass != ENCHANTER) && (InviserClass != WIZARD) - || (InviserClass != MAGICIAN)){ - Inviser = g->members[i]; - InviserClass = DRUID; - } - break; - default: - break; - } - } - } - switch(InviserClass) { - case ENCHANTER: - if ((c->GetLevel() <= 14) && (!strcasecmp(sep->arg[2], "undead"))) - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I'm not level 14 yet."); - else if ((!c->IsInvisible(c)) && (!c->invisible_undead) && (c->GetLevel() >= 14) && (!strcasecmp(sep->arg[2], "undead"))) { - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting Invisibility vs. Undead."); - Inviser->CastSpell(235, c->GetID(), 1, -1, -1); - } - else if ((c->GetLevel() <= 4) && (!strcasecmp(sep->arg[2], "live"))) - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I'm not level 4 yet."); - else if ((!c->IsInvisible(c))&& (!c->invisible_undead) && (c->GetLevel() >= 4) && (!strcasecmp(sep->arg[2], "live"))) { - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting Invisibilty."); - Inviser->CastSpell(42, c->GetID(), 1, -1, -1); - } - else if ((c->GetLevel() <= 6) && (!strcasecmp(sep->arg[2], "see"))) - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I'm not level 6 yet."); - else if ((c->GetLevel() >= 6) && (!strcasecmp(sep->arg[2], "see"))) { - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting See Invisibility."); - Inviser->CastSpell(80, c->GetID(), 1, -1, -1); - } - else if ((c->IsInvisible(c)) || (c->invisible_undead)) - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I can't cast this if you're already invis-buffed."); - else - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Do you want %s, %s, or %s?", Inviser->CastToBot()->CreateSayLink(c, "#bot invis live", "Invisibility").c_str(), Inviser->CastToBot()->CreateSayLink(c, "#bot invis undead", "Invisibility vs. Undead").c_str(), Inviser->CastToBot()->CreateSayLink(c, "#bot invis see", "See Invisibility").c_str()); - break; - case MAGICIAN: - if (!strcasecmp(sep->arg[2], "undead")) - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I don't have that spell."); - else if ((c->GetLevel() <= 8) && (!strcasecmp(sep->arg[2], "live"))) - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I'm not level 8 yet."); - else if ((!c->IsInvisible(c))&& (!c->invisible_undead) && (c->GetLevel() >= 8) && (!strcasecmp(sep->arg[2], "live"))) { - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting Invisibilty."); - Inviser->CastSpell(42, c->GetID(), 1, -1, -1); - } - else if ((c->GetLevel() <= 16) && (!strcasecmp(sep->arg[2], "see"))) - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I'm not level 16 yet."); - else if ((c->GetLevel() >= 16) && (!strcasecmp(sep->arg[2], "see"))) { - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting See Invisibility."); - Inviser->CastSpell(80, c->GetID(), 1, -1, -1); - } - else if ((c->IsInvisible(c)) || (c->invisible_undead)) - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I can't cast this if you're already invis-buffed."); - else - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Do you want %s or %s?", Inviser->CastToBot()->CreateSayLink(c, "#bot invis live", "Invisibility").c_str(), Inviser->CastToBot()->CreateSayLink(c, "#bot invis see", "see Invisibility").c_str()); - break; - case WIZARD: - if ((c->GetLevel() <= 39) && (!strcasecmp(sep->arg[2], "undead"))) - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I'm not level 39 yet."); - else if ((!c->IsInvisible(c))&& (!c->invisible_undead) && (c->GetLevel() >= 39) && (!strcasecmp(sep->arg[2], "undead"))) { - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting Invisibility vs. Undead."); - Inviser->CastSpell(235, c->GetID(), 1, -1, -1); - } - else if ((c->GetLevel() <= 16) && (!strcasecmp(sep->arg[2], "live"))) - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I'm not level 16 yet."); - else if ((!c->IsInvisible(c))&& (!c->invisible_undead) && (c->GetLevel() >= 16) && (!strcasecmp(sep->arg[2], "live"))) { - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting Invisibilty."); - Inviser->CastSpell(42, c->GetID(), 1, -1, -1); - } - else if ((c->GetLevel() <= 4) && (!strcasecmp(sep->arg[2], "see"))) - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I'm not level 6 yet."); - else if ((c->GetLevel() >= 4) && (!strcasecmp(sep->arg[2], "see"))) { - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting See Invisibility."); - Inviser->CastSpell(80, c->GetID(), 1, -1, -1); - } - else if ((c->IsInvisible(c)) || (c->invisible_undead)) - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I can't cast this if you're already invis-buffed."); - else - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Do you want %s, %s, or %s?", Inviser->CastToBot()->CreateSayLink(c, "#bot invis undead", "Invisibility vs. Undead").c_str(), Inviser->CastToBot()->CreateSayLink(c, "#bot invis live", "Invisibility").c_str(), Inviser->CastToBot()->CreateSayLink(c, "#bot invis see", "See Invisibility").c_str()); - break; - case NECROMANCER: - if ((!c->IsInvisible(c))&& (!c->invisible_undead) && (!strcasecmp(sep->arg[2], "undead"))) { - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting Invisibility vs. Undead."); - Inviser->CastSpell(235, c->GetID(), 1, -1, -1); - } - else if (!strcasecmp(sep->arg[2], "see")) - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I don't have that spell."); - else if (!strcasecmp(sep->arg[2], "live")) - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I don't have that spell."); - else if ((c->IsInvisible(c))|| (c->invisible_undead)) - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I can't cast this if you're already invis-buffed."); - else - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I only have %s.", Inviser->CastToBot()->CreateSayLink(c, "#bot invis undead", "Invisibility vs. Undead").c_str()); - break; - case DRUID: - if (!strcasecmp(sep->arg[2], "undead")) - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I don't have that spell."); - else if ((c->GetLevel() <= 4) && (!strcasecmp(sep->arg[2], "live"))) - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I'm not level 4 yet."); - else if ((!c->IsInvisible(c))&& (!c->invisible_undead) && (c->GetLevel() >= 18) && (!strcasecmp(sep->arg[2], "live"))) { - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting Superior Camouflage."); - Inviser->CastSpell(34, c->GetID(), 1, -1, -1); - } else if ((!c->IsInvisible(c))&& (!c->invisible_undead) && (c->GetLevel() >= 4) && (!strcasecmp(sep->arg[2], "live")) && (zone->CanCastOutdoor())) { - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting Camouflage."); - Inviser->CastSpell(247, c->GetID(), 1, -1, -1); - } - else if ((c->GetLevel() >= 4) && (!strcasecmp(sep->arg[2], "live")) && (!zone->CanCastOutdoor())) - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I can't cast this spell indoors."); - else if ((c->GetLevel() <= 13) && (!strcasecmp(sep->arg[2], "see"))) - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I'm not level 13 yet."); - else if ((c->GetLevel() >= 13) && (!strcasecmp(sep->arg[2], "see"))) { - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting See Invisibility."); - Inviser->CastSpell(80, c->GetID(), 1, -1, -1); - } - else if ((c->IsInvisible(c)) || (c->invisible_undead)) - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I can't cast this if you're already invis-buffed."); - else - Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Do you want %s or %s?", Inviser->CastToBot()->CreateSayLink(c, "#bot invis live", "Invisibility").c_str(), Inviser->CastToBot()->CreateSayLink(c, "#bot invis see", "See Invisibility").c_str()); - break; - default: - c->Message(15, "You must have a Enchanter, Magician, Wizard, Druid, or Necromancer in your group."); - break; - } - } - } - - //Levitate - if ((!strcasecmp(sep->arg[1], "levitate")) && (c->IsGrouped())) { - Mob *Lever; - uint32 LeverClass = 0; - Group *g = c->GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++){ - if(g->members[i] && g->members[i]->IsBot()) { - switch(g->members[i]->GetClass()) { - case DRUID: - Lever = g->members[i]; - LeverClass = DRUID; - break; - case SHAMAN: - if (LeverClass != DRUID){ - Lever = g->members[i]; - LeverClass = SHAMAN; - } - break; - case WIZARD: - if(LeverClass == 0){ - Lever = g->members[i]; - LeverClass = WIZARD; - } - break; - case ENCHANTER: - if (LeverClass == 0) { - Lever = g->members[i]; - LeverClass = ENCHANTER; - } - break; - default: - break; - } - } - } - switch(LeverClass) { - case DRUID: - if (c->GetLevel() <= 14) - Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "I'm not level 14 yet."); - else if (zone->CanCastOutdoor()) { - Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "Casting Levitate."); - Lever->CastSpell(261, c->GetID(), 1, -1, -1); - break; - } - else if (!zone->CanCastOutdoor()) - Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "I can't cast this spell indoors."); - break; - case SHAMAN: - if ((zone->CanCastOutdoor()) && (c->GetLevel() >= 10)) { - Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "Casting Levitate."); - Lever->CastToClient()->CastSpell(261, c->GetID(), 1, -1, -1); - } - else if (!zone->CanCastOutdoor()) - Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "I can't cast this spell indoors."); - else if (c->GetLevel() <= 10) - Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "I'm not level 10 yet."); - break; - case WIZARD: - if((zone->CanCastOutdoor()) && (c->GetLevel() >= 22)) { - Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "Casting Levitate."); - Lever->CastToClient()->CastSpell(261, c->GetID(), 1, -1, -1); - } - else if (!zone->CanCastOutdoor()) - Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "I can't cast this spell indoors."); - else if (c->GetLevel() <= 22) - Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "I'm not level 22 yet."); - break; - case ENCHANTER: - if((zone->CanCastOutdoor()) && (c->GetLevel() >= 15)) { - Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "Casting Levitate."); - Lever->CastToClient()->CastSpell(261, c->GetID(), 1, -1, -1); - } - else if (!zone->CanCastOutdoor()) - Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "I can't cast this spell indoors."); - else if (c->GetLevel() <= 15) - Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "I'm not level 15 yet."); - break; - default: - c->Message(15, "You must have a Druid, Shaman, Wizard, or Enchanter in your group."); - break; - } - } - } - - //Resists - if ((!strcasecmp(sep->arg[1], "resist")) && (c->IsGrouped())) { - Mob *Resister; - uint32 ResisterClass = 0; - Group *g = c->GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++){ - if(g->members[i] && g->members[i]->IsBot()) { - switch(g->members[i]->GetClass()) { - case CLERIC: - Resister = g->members[i]; - ResisterClass = CLERIC; - break; - case SHAMAN: - if(ResisterClass != CLERIC){ - Resister = g->members[i]; - ResisterClass = SHAMAN; - } - break; - case DRUID: - if (ResisterClass == 0){ - Resister = g->members[i]; - ResisterClass = DRUID; - } - break; - default: - break; - } - } - } - switch(ResisterClass) { - case CLERIC: - if(!strcasecmp(sep->arg[2], "poison") && (c->GetLevel() >= 6)) { - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting Poison protection."); - Resister->CastToBot()->Bot_Command_Resist(1, Resister->GetLevel()); - } else if(!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() >= 11)) { - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting Disease protection."); - Resister->CastToBot()->Bot_Command_Resist(2, Resister->GetLevel()); - } else if(!strcasecmp(sep->arg[2], "fire") && (c->GetLevel() >= 8)) { - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting Fire protection."); - Resister->CastToBot()->Bot_Command_Resist(3, Resister->GetLevel()); - } else if(!strcasecmp(sep->arg[2], "cold") && (c->GetLevel() >= 13)) { - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting Cold protection."); - Resister->CastToBot()->Bot_Command_Resist(4, Resister->GetLevel()); - } else if(!strcasecmp(sep->arg[2], "magic") && (c->GetLevel() >= 16)) { - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting Magic protection."); - Resister->CastToBot()->Bot_Command_Resist(5, Resister->GetLevel()); - } else if(!strcasecmp(sep->arg[2], "magic") && (c->GetLevel() <= 16) - || !strcasecmp(sep->arg[2], "cold") && (c->GetLevel() <= 13) - || !strcasecmp(sep->arg[2], "fire") && (c->GetLevel() <= 8) - || !strcasecmp(sep->arg[2], "disease") && (c->GetLevel() <= 11) - || !strcasecmp(sep->arg[2], "poison") && (c->GetLevel() <= 6)) { - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "I am not the required level yet."); - } else - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Do you want %s, %s, %s, %s, or %s?", Resister->CastToBot()->CreateSayLink(c, "#bot resist poison", "Resist Poison").c_str(), Resister->CastToBot()->CreateSayLink(c, "#bot resist disease", "Resist Disease").c_str(), Resister->CastToBot()->CreateSayLink(c, "#bot resist fire", "Resist Fire").c_str(), Resister->CastToBot()->CreateSayLink(c, "#bot resist cold", "Resist Cold").c_str(), Resister->CastToBot()->CreateSayLink(c, "#bot resist magic", "Resist Magic").c_str()); - - break; - case SHAMAN: - if(!strcasecmp(sep->arg[2], "poison") && (c->GetLevel() >= 20)) { - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting Poison protection."); - Resister->CastToBot()->Bot_Command_Resist(12, Resister->GetLevel()); - } else if(!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() >= 8)) { - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting Disease protection."); - Resister->CastToBot()->Bot_Command_Resist(13, Resister->GetLevel()); - } else if(!strcasecmp(sep->arg[2], "fire") && (c->GetLevel() >= 5)) { - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting Fire protection."); - Resister->CastToBot()->Bot_Command_Resist(14, Resister->GetLevel()); - } else if(!strcasecmp(sep->arg[2], "cold") && (c->GetLevel() >= 1)) { - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting Cold protection."); - Resister->CastToBot()->Bot_Command_Resist(15, Resister->GetLevel()); - } else if(!strcasecmp(sep->arg[2], "magic") && (c->GetLevel() >= 19)) { - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting Magic protection."); - Resister->CastToBot()->Bot_Command_Resist(16, Resister->GetLevel()); - } else if(!strcasecmp(sep->arg[2], "magic") && (c->GetLevel() <= 19) - || !strcasecmp(sep->arg[2], "cold") && (c->GetLevel() <= 1) - || !strcasecmp(sep->arg[2], "fire") && (c->GetLevel() <= 5) - || !strcasecmp(sep->arg[2], "disease") && (c->GetLevel() <= 8) - || !strcasecmp(sep->arg[2], "poison") && (c->GetLevel() <= 20)) { - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "I am not the required level yet."); - } else - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Do you want %s, %s, %s, %s, or %s?", Resister->CastToBot()->CreateSayLink(c, "#bot resist poison", "Resist Poison").c_str(), Resister->CastToBot()->CreateSayLink(c, "#bot resist disease", "Resist Disease").c_str(), Resister->CastToBot()->CreateSayLink(c, "#bot resist fire", "Resist Fire").c_str(), Resister->CastToBot()->CreateSayLink(c, "#bot resist cold", "Resist Cold").c_str(), Resister->CastToBot()->CreateSayLink(c, "#bot resist magic", "Resist Magic").c_str()); - - break; - case DRUID: - if (!strcasecmp(sep->arg[2], "poison") && (c->GetLevel() >= 19)) { - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting Poison protection."); - Resister->CastToBot()->Bot_Command_Resist(7, Resister->GetLevel()); - } else if (!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() >= 19)) { - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting Disease protection."); - Resister->CastToBot()->Bot_Command_Resist(8, Resister->GetLevel()); - } else if(!strcasecmp(sep->arg[2], "fire")) { // Fire level 1 - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting Fire protection."); - Resister->CastToBot()->Bot_Command_Resist(9, Resister->GetLevel()); - } else if(!strcasecmp(sep->arg[2], "cold") && (c->GetLevel() >= 13)) { - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting Cold protection."); - Resister->CastToBot()->Bot_Command_Resist(10, Resister->GetLevel()); - } else if(!strcasecmp(sep->arg[2], "magic") && (c->GetLevel() >= 16)) { - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting Magic protection."); - Resister->CastToBot()->Bot_Command_Resist(11, Resister->GetLevel()); - } else if (!strcasecmp(sep->arg[2], "magic") && (c->GetLevel() <= 16) - || !strcasecmp(sep->arg[2], "cold") && (c->GetLevel() <= 9) - || !strcasecmp(sep->arg[2], "disease") && (c->GetLevel() <= 19) - || !strcasecmp(sep->arg[2], "poison") && (c->GetLevel() <= 19)) { - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "I am not the required level yet.") ; - } else - Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Do you want %s, %s, %s, %s, or %s?", Resister->CastToBot()->CreateSayLink(c, "#bot resist poison", "Resist Poison").c_str(), Resister->CastToBot()->CreateSayLink(c, "#bot resist disease", "Resist Disease").c_str(), Resister->CastToBot()->CreateSayLink(c, "#bot resist fire", "Resist Fire").c_str(), Resister->CastToBot()->CreateSayLink(c, "#bot resist cold", "Resist Cold").c_str(), Resister->CastToBot()->CreateSayLink(c, "#bot resist magic", "Resist Magic").c_str()); - break; - default: - c->Message(15, "You must have a Cleric, Shaman, or Druid in your group."); - break; - } - } - } - - // #bot group ... - if(!strcasecmp(sep->arg[1], "group") && !strcasecmp(sep->arg[2], "help")) { - c->Message(0, "#bot group help - will show this help."); - c->Message(0, "#bot group summon . Summons the bot group to your location."); - c->Message(0, "#bot group follow "); - c->Message(0, "#bot group guard "); - c->Message(0, "#bot group attack "); - return; - } - - if(!strcasecmp(sep->arg[1], "group")) { - if(!strcasecmp(sep->arg[2], "follow")) { - if(c->IsGrouped()) - BotGroupOrderFollow(c->GetGroup(), c); - } else if(!strcasecmp(sep->arg[2], "guard")) { - if(c->IsGrouped()) - BotGroupOrderGuard(c->GetGroup(), c); - } else if(!strcasecmp(sep->arg[2], "attack")) { - if(c->IsGrouped() && (c->GetTarget() != nullptr) && c->IsAttackAllowed(c->GetTarget())) - BotGroupOrderAttack(c->GetGroup(), c->GetTarget(), c); - else - c->Message(15, "You must target a monster."); - } else if(!strcasecmp(sep->arg[2], "summon")) { - if(c->IsGrouped()) - BotGroupSummon(c->GetGroup(), c); - } - return; - } - - // #bot botgroup ... - if(!strcasecmp(sep->arg[1], "botgroup") && !strcasecmp(sep->arg[2], "help")) { - c->Message(0, "#bot botgroup help - will show this help."); - c->Message(0, "#bot botgroup create . This will designate a bot to be a bot group leader."); - c->Message(0, "#bot botgroup add "); - c->Message(0, "#bot botgroup remove "); - c->Message(0, "#bot botgroup disband . Disbands the designated bot group leader's bot group."); - c->Message(0, "#bot botgroup summon . Summons the bot group to your location."); - c->Message(0, "#bot botgroup follow "); - c->Message(0, "#bot botgroup guard "); - c->Message(0, "#bot botgroup attack "); - c->Message(0, "#bot botgroup list"); - c->Message(0, "#bot botgroup load "); - c->Message(0, "#bot botgroup save "); - c->Message(0, "#bot botgroup delete "); - - return; - } - - if(!strcasecmp(sep->arg[1], "botgroup") && !strcasecmp(sep->arg[2], "create")) { - Mob* targetMob = c->GetTarget(); - std::string targetName = std::string(sep->arg[3]); - Bot* botGroupLeader = 0; - if(!targetName.empty()) - botGroupLeader = entity_list.GetBotByBotName(targetName); - else if(targetMob) { - if(targetMob->IsBot()) - botGroupLeader = targetMob->CastToBot(); - } - - if(botGroupLeader) { - if(Bot::BotGroupCreate(botGroupLeader)) - botGroupLeader->BotGroupSay(botGroupLeader, "I am prepared to lead."); - else - botGroupLeader->BotGroupSay(botGroupLeader, "I cannot lead."); - } - else - c->Message(13, "You must target a spawned bot first."); - - return; - } - - if(!strcasecmp(sep->arg[1], "botgroup") && !strcasecmp(sep->arg[2], "add")) { - int argCount = 0; - argCount = sep->argnum; - std::string botGroupLeaderName; - std::string botGroupMemberName; - if(argCount >= 3) - botGroupMemberName = std::string(sep->arg[3]); - - Bot* botGroupMember = entity_list.GetBotByBotName(botGroupMemberName); - if(!botGroupMember) { - if(botGroupMemberName.empty()) - c->Message(13, "You must target a bot in this zone. Please try again."); - else - c->Message(13, "%s is not a bot in this zone. Please try again.", botGroupMemberName.c_str()); - - return; - } - - Bot* botGroupLeader = 0; - if(argCount == 4) { - botGroupLeaderName = std::string(sep->arg[4]); - botGroupLeader = entity_list.GetBotByBotName(botGroupLeaderName); - } else if(c->GetTarget() && c->GetTarget()->IsBot()) - botGroupLeader = c->GetTarget()->CastToBot(); - - if(!botGroupLeader) { - if(botGroupLeaderName.empty()) - c->Message(13, "You must target a bot in this zone. Please try again."); - else - c->Message(13, "%s is not a bot in this zone. Please try again.", botGroupLeaderName.c_str()); - - return; - } - - if(botGroupLeader->HasGroup()) { - Group* g = botGroupLeader->GetGroup(); - - if(g) { - if(g->IsLeader(botGroupLeader)) { - if(g->GroupCount() < MAX_GROUP_MEMBERS) { - if(!botGroupMemberName.empty() && botGroupMember) { - botGroupMember = entity_list.GetBotByBotName(botGroupMemberName); - } - - if(botGroupMember) { - if(!botGroupMember->HasGroup()) { - if(Bot::AddBotToGroup(botGroupMember, g)) { - database.SetGroupID(botGroupMember->GetName(), g->GetID(), botGroupMember->GetBotID()); - botGroupMember->BotGroupSay(botGroupMember, "I have joined %s\'s group.", botGroupLeader->GetName()); - } - else - botGroupMember->BotGroupSay(botGroupMember, "I can not join %s\'s group.", botGroupLeader->GetName()); - } else { - Group* tempGroup = botGroupMember->GetGroup(); - if(tempGroup) - botGroupMember->BotGroupSay(botGroupMember, "I can not join %s\'s group. I am already a member in %s\'s group.", botGroupLeader->GetName(), tempGroup->GetLeaderName()); - } - } - else - c->Message(13, "You must target a spawned bot first."); - } - else - botGroupLeader->BotGroupSay(botGroupMember, "I have no more openings in my group, %s.", c->GetName()); - } else { - Group* tempGroup = botGroupLeader->GetGroup(); - if(tempGroup) - botGroupLeader->BotGroupSay(botGroupLeader, "I can not lead anyone because I am a member in %s\'s group.", tempGroup->GetLeaderName()); - } - } - } - return; - } - - if(!strcasecmp(sep->arg[1], "botgroup") && !strcasecmp(sep->arg[2], "remove")) { - Mob* targetMob = c->GetTarget(); - std::string targetName = std::string(sep->arg[3]); - Bot* botGroupMember = 0; - - if(!targetName.empty()) - botGroupMember = entity_list.GetBotByBotName(targetName); - else if(targetMob) { - if(targetMob->IsBot()) - botGroupMember = targetMob->CastToBot(); - } - - if(botGroupMember) { - if(botGroupMember->HasGroup()) { - Group* g = botGroupMember->GetGroup(); - if(Bot::RemoveBotFromGroup(botGroupMember, g)) - botGroupMember->BotGroupSay(botGroupMember, "I am no longer in a group."); - else - botGroupMember->BotGroupSay(botGroupMember, "I can not leave %s\'s group.", g->GetLeaderName()); - } - else - botGroupMember->BotGroupSay(botGroupMember, "I am not in a group."); - } - else - c->Message(13, "You must target a spawned bot first."); - - return; - } - - if(!strcasecmp(sep->arg[1], "botgroup") && !strcasecmp(sep->arg[2], "disband")) { - Mob* targetMob = c->GetTarget(); - std::string targetName = std::string(sep->arg[3]); - Bot* botGroupLeader = 0; - if(!targetName.empty()) - botGroupLeader = entity_list.GetBotByBotName(targetName); - else if(targetMob) { - if(targetMob->IsBot()) - botGroupLeader = targetMob->CastToBot(); - } - - if(botGroupLeader) { - if(botGroupLeader->HasGroup()) { - Group* g = botGroupLeader->GetGroup(); - if(g->IsLeader(botGroupLeader)) { - if(Bot::RemoveBotFromGroup(botGroupLeader, g)) - botGroupLeader->BotGroupSay(botGroupLeader, "I have disbanded my group, %s.", c->GetName()); - else - botGroupLeader->BotGroupSay(botGroupLeader, "I was not able to disband my group, %s.", c->GetName()); - } - else - botGroupLeader->BotGroupSay(botGroupLeader, "I can not disband my group, %s, because I am not the leader. %s is the leader of my group.", c->GetName(), g->GetLeaderName()); - } else - botGroupLeader->BotGroupSay(botGroupLeader, "I am not a group leader, %s.", c->GetName()); - } else - c->Message(13, "You must target a spawned bot group leader first."); - - return; - } - - if(!strcasecmp(sep->arg[1], "botgroup") && !strcasecmp(sep->arg[2], "summon") ) { - Mob* targetMob = c->GetTarget(); - std::string targetName = std::string(sep->arg[3]); - Bot* botGroupLeader = 0; - - if(!targetName.empty()) - botGroupLeader = entity_list.GetBotByBotName(targetName); - else if(targetMob) { - if(targetMob->IsBot()) - botGroupLeader = targetMob->CastToBot(); - } - - if(botGroupLeader) { - if(botGroupLeader->HasGroup()) { - Group* g = botGroupLeader->GetGroup(); - if(g->IsLeader(botGroupLeader)) - BotGroupSummon(g, c); - } - } - else if(c->HasGroup()) - BotGroupSummon(c->GetGroup(), c); - - return; - } - - if(!strcasecmp(sep->arg[1], "botgroup") && !strcasecmp(sep->arg[2], "follow") ) { - Mob* targetMob = c->GetTarget(); - std::string targetName = std::string(sep->arg[3]); - Bot* botGroupLeader = 0; - if(!targetName.empty()) - botGroupLeader = entity_list.GetBotByBotName(targetName); - else if(targetMob) { - if(targetMob->IsBot()) - botGroupLeader = targetMob->CastToBot(); - } - - if(botGroupLeader) { - if(botGroupLeader->HasGroup()) { - Group* g = botGroupLeader->GetGroup(); - if(g->IsLeader(botGroupLeader)) - BotGroupOrderFollow(g, c); - } - } - else if(c->HasGroup()) - BotGroupOrderFollow(c->GetGroup(), c); - - return; - } - - if(!strcasecmp(sep->arg[1], "botgroup") && !strcasecmp(sep->arg[2], "guard") ) { - Mob* targetMob = c->GetTarget(); - std::string targetName = std::string(sep->arg[3]); - Bot* botGroupLeader = 0; - if(!targetName.empty()) - botGroupLeader = entity_list.GetBotByBotName(targetName); - else if(targetMob) { - if(targetMob->IsBot()) - botGroupLeader = targetMob->CastToBot(); - } - - if(botGroupLeader) { - if(botGroupLeader->HasGroup()) { - Group* g = botGroupLeader->GetGroup(); - if(g->IsLeader(botGroupLeader)) - BotGroupOrderGuard(g, c); - } - } - else if(c->HasGroup()) - BotGroupOrderGuard(c->GetGroup(), c); - - return; - } - - if(!strcasecmp(sep->arg[1], "botgroup") && !strcasecmp(sep->arg[2], "attack")) { - Mob* targetMob = c->GetTarget(); - Bot* botGroupLeader = 0; - std::string botGroupLeaderName = std::string(sep->arg[3]); - std::string targetName = std::string(sep->arg[4]); - if(!botGroupLeaderName.empty()) { - botGroupLeader = entity_list.GetBotByBotName(botGroupLeaderName); - if(botGroupLeader) { - if(!targetName.empty()) - targetMob = entity_list.GetMob(targetName.c_str()); - - if(targetMob) { - if(c->IsAttackAllowed(targetMob)) { - if(botGroupLeader->HasGroup()) { - Group* g = botGroupLeader->GetGroup(); - if(g) { - if(g->IsLeader(botGroupLeader)) - BotGroupOrderAttack(g, targetMob, c); - } - } - else if(c->HasGroup()) - BotGroupOrderAttack(c->GetGroup(), targetMob, c); - } - else - c->Message(13, "You must target a monster."); - } - else - c->Message(13, "You must target a monster."); - } - else - c->Message(13, "You must target a spawned bot group leader first."); - } - return; - } - - if(!strcasecmp(sep->arg[1], "botgroup") && !strcasecmp(sep->arg[2], "list")) { - std::list botGroupList = GetBotGroupListByBotOwnerCharacterId(c->CharacterID(), &TempErrorMessage); - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - return; - } - - if(!botGroupList.empty()) { - for(std::list::iterator botGroupListItr = botGroupList.begin(); botGroupListItr != botGroupList.end(); ++botGroupListItr) - c->Message(0, "Bot Group Name: %s -- Bot Group Leader: %s", botGroupListItr->BotGroupName.c_str(), botGroupListItr->BotGroupLeaderName.c_str()); - } - else - c->Message(0, "You have no bot groups created. Use the #bot botgroup save command to save bot groups."); - - return; - } - - if(!strcasecmp(sep->arg[1], "botgroup") && !strcasecmp(sep->arg[2], "load")) { - Group *g = c->GetGroup(); - if(g) { - for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(!g->members[i]) - continue; - - if((g->members[i]->IsClient() && g->members[i]->CastToClient()->GetAggroCount()) || g->members[i]->IsEngaged()) { - c->Message(0, "You can't spawn bots while your group is engaged."); - return; - } - } - } else { - if(c->GetAggroCount() > 0) { - c->Message(0, "You can't spawn bots while you are engaged."); - return; - } - } - - std::string botGroupName = std::string(sep->arg[3]); - if(botGroupName.empty()) { - c->Message(13, "Invalid botgroup name supplied."); - return; - } - - uint32 botGroupID = CanLoadBotGroup(c->CharacterID(), botGroupName, &TempErrorMessage); - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - return; - } - if(botGroupID <= 0) { - c->Message(13, "Invalid botgroup id found."); - return; - } - - std::list botGroup = LoadBotGroup(botGroupName, &TempErrorMessage); - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - return; - } - - int spawnedBots = SpawnedBotCount(c->CharacterID(), &TempErrorMessage); - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - return; - } - - if(RuleB(Bots, QuestableSpawnLimit)) { - const int allowedBotsBQ = AllowedBotSpawns(c->CharacterID(), &TempErrorMessage); - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - return; - } - - if(allowedBotsBQ == 0) { - c->Message(0, "You can't spawn any bots."); - return; - } - - if(spawnedBots >= allowedBotsBQ || spawnedBots + (int)botGroup.size() > allowedBotsBQ) { - c->Message(0, "You can't spawn more than %i bot(s).", allowedBotsBQ); - return; - } - } - - const int allowedBotsSBC = RuleI(Bots, SpawnLimit); - if(spawnedBots >= allowedBotsSBC || spawnedBots + (int)botGroup.size() > allowedBotsSBC) { - c->Message(0, "You can't spawn more than %i bots.", allowedBotsSBC); - return; - } - - uint32 botGroupLeaderBotID = GetBotGroupLeaderIdByBotGroupName(botGroupName); - Bot *botGroupLeader = LoadBot(botGroupLeaderBotID, &TempErrorMessage); - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - safe_delete(botGroupLeader); - return; - } - if(!botGroupLeader) { - c->Message(13, "Failed to load botgroup leader."); - safe_delete(botGroupLeader); - return; - } - - botGroupLeader->Spawn(c, &TempErrorMessage); - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - safe_delete(botGroupLeader); - return; - } - - if(!BotGroupCreate(botGroupLeader)) { - c->Message(13, "Unable to create botgroup."); - return; - } - - Group *newBotGroup = botGroupLeader->GetGroup(); - if(!newBotGroup) { - c->Message(13, "Unable to find valid botgroup"); - return; - } - - for(auto botGroupItr = botGroup.begin(); botGroupItr != botGroup.end(); ++botGroupItr) { - if(botGroupItr->BotID == botGroupLeader->GetBotID()) - continue; - - Bot *botGroupMember = LoadBot(botGroupItr->BotID, &TempErrorMessage); - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - safe_delete(botGroupMember); - return; - } - - if(!botGroupMember) { - safe_delete(botGroupMember); - continue; - } - - botGroupMember->Spawn(c, &TempErrorMessage); - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - safe_delete(botGroupMember); - return; - } - - AddBotToGroup(botGroupMember, newBotGroup); - } - return; - } - - if(!strcasecmp(sep->arg[1], "botgroup") && !strcasecmp(sep->arg[2], "delete")) { - std::string botGroupName = std::string(sep->arg[3]); - if(!botGroupName.empty()) { - uint32 botGroupId = CanLoadBotGroup(c->CharacterID(), botGroupName, &TempErrorMessage); - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - return; - } - - if(botGroupId > 0) { - DeleteBotGroup(botGroupName, &TempErrorMessage); - if(!TempErrorMessage.empty()) { - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - return; - } - } - } - return; - } - - if(!strcasecmp(sep->arg[1], "botgroup") && !strcasecmp(sep->arg[2], "save")) { - std::string botGroupName = std::string(sep->arg[3]); - if(!botGroupName.empty()) { - if(!DoesBotGroupNameExist(botGroupName)) { - Bot* groupLeader = 0; - if(c->GetTarget() && c->GetTarget()->IsBot()) - groupLeader = c->GetTarget()->CastToBot(); - else - groupLeader = entity_list.GetBotByBotName(std::string(sep->arg[4])); - - if(groupLeader) { - if(groupLeader->HasGroup() && groupLeader->GetGroup()->IsLeader(groupLeader)) { - SaveBotGroup(groupLeader->GetGroup(), botGroupName, &TempErrorMessage); - if(!TempErrorMessage.empty()) - c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - else - c->Message(0, "%s's bot group has been saved as %s.", groupLeader->GetName(), botGroupName.c_str()); - } - else - c->Message(0, "You must target a bot group leader only."); - } - else - c->Message(0, "You must target a bot that is in the same zone as you."); - } - else - c->Message(0, "The bot group name already exists. Please choose another name to save your bot group as."); - } - return; - } - - if(!strcasecmp(sep->arg[1], "haircolor") || !strcasecmp(sep->arg[1], "hair") || !strcasecmp(sep->arg[1], "beard") || !strcasecmp(sep->arg[1], "beardcolor") || !strcasecmp(sep->arg[1], "face") - || !strcasecmp(sep->arg[1], "eyes") || !strcasecmp(sep->arg[1], "heritage") || !strcasecmp(sep->arg[1], "tattoo") || !strcasecmp(sep->arg[1], "details")) { - if(c->GetTarget() && c->GetTarget()->IsBot()) { - if (sep->IsNumber(2)) { - if (c->GetTarget()->CastToBot()->GetBotOwnerCharacterID() == c->CharacterID()) { - Bot *target = c->GetTarget()->CastToBot(); - uint16 Race = target->GetRace(); - uint8 Gender = target->GetGender(); - uint8 Texture = 0xFF; - uint8 HelmTexture = 0xFF; - uint8 HairStyle = target->GetHairStyle(); - uint8 HairColor = target->GetHairColor(); - uint8 BeardColor = target->GetBeardColor(); - uint8 EyeColor1 = target->GetEyeColor1(); - uint8 EyeColor2 = target->GetEyeColor2(); - uint8 LuclinFace = target->GetLuclinFace(); - uint8 Beard = target->GetBeard(); - uint32 DrakkinHeritage = target->GetDrakkinHeritage(); - uint32 DrakkinTattoo = target->GetDrakkinTattoo(); - uint32 DrakkinDetails = target->GetDrakkinDetails(); - float Size = target->GetSize(); - if (!strcasecmp(sep->arg[1], "hair")) - HairStyle = atoi(sep->arg[2]); - - if (!strcasecmp(sep->arg[1], "haircolor")) - HairColor = atoi(sep->arg[2]); - - if (!strcasecmp(sep->arg[1], "beard") || !strcasecmp(sep->arg[1], "beardcolor")) { - if (!Gender || Race == 8) { - if (!strcasecmp(sep->arg[1], "beard")) - Beard = atoi(sep->arg[2]); - - if (!strcasecmp(sep->arg[1], "beardcolor")) - BeardColor = atoi(sep->arg[2]); - } else { - c->Message(0, "Must be a male bot, or dwarf."); - return; - } - } - - if (!strcasecmp(sep->arg[1], "face")) - LuclinFace = atoi(sep->arg[2]); - - if (!strcasecmp(sep->arg[1], "eyes")) { - EyeColor1 = EyeColor2 = atoi(sep->arg[2]); - c->Message(0, "Eye Values = 0 - 11"); - } - - if(!strcasecmp(sep->arg[1], "heritage") || !strcasecmp(sep->arg[1], "tattoo") || !strcasecmp(sep->arg[1], "details")) { - if(Race == 522) { - if(!strcasecmp(sep->arg[1], "heritage")) { - DrakkinHeritage = atoi(sep->arg[2]); - c->Message(0, "Heritage Values = 0 - 6"); - } - - if(!strcasecmp(sep->arg[1], "tattoo")) { - DrakkinTattoo = atoi(sep->arg[2]); - c->Message(0, "Tattoo Values = 0 - 7"); - } - - if(!strcasecmp(sep->arg[1], "details")) { - DrakkinDetails = atoi(sep->arg[2]); - c->Message(0, "Details Values = 0 - 7"); - } - } else { - c->Message(0, "Drakkin only."); - return; - } - } - - target->SendIllusionPacket(Race, Gender, Texture, HelmTexture, HairColor, BeardColor, EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, DrakkinHeritage, DrakkinTattoo, DrakkinDetails, Size); - if(target->CastToBot()->Save()) - c->Message(0, "%s saved.", target->GetCleanName()); - else - c->Message(13, "%s save failed!", target->GetCleanName()); - - c->Message(0, "Feature changed."); - } - else - c->Message(0, "You must own the bot to make changes."); - } - else - c->Message(0, "Requires a value."); - } - else - c->Message(0, "A bot needs to be targeted."); - return; - } - - if(!strcasecmp(sep->arg[1], "taunt")) { - bool taunt = false; - bool toggle = false; - - if(sep->arg[2]){ - if(!strcasecmp(sep->arg[2], "on")) - taunt = true; - else if (!strcasecmp(sep->arg[2], "off")) - taunt = false; - else { - c->Message(0, "Usage #bot taunt [on|off]"); - return; - } - - Bot *targetedBot = nullptr; - if(c->GetTarget() != nullptr) { - if (c->GetTarget()->IsBot() && (c->GetTarget()->CastToBot()->GetBotOwner() == c)) - targetedBot = c->GetTarget()->CastToBot(); - else - c->Message(13, "You must target a bot that you own."); - - if(targetedBot) { - if(targetedBot->GetSkill(SkillTaunt) > 0) { - if(toggle) - taunt = !targetedBot->taunting; - - if(taunt) { - if(!targetedBot->taunting) - targetedBot->BotGroupSay(targetedBot, "I am now taunting."); - } else { - if(targetedBot->taunting) - targetedBot->BotGroupSay(targetedBot, "I am no longer taunting."); - } - - targetedBot->SetTaunting(taunt); - } - else - c->Message(13, "You must select a bot with the taunt skill."); - } - else - c->Message(13, "You must target a spawned bot."); - } - } - else - c->Message(0, "Usage #bot taunt [on|off]"); - - return; - } - - if(!strcasecmp(sep->arg[1], "stance")) { - if(sep->argnum == 3){ - Bot* tempBot = nullptr; - std::string botName = std::string(sep->arg[2]); - if(!botName.empty()) - tempBot = entity_list.GetBotByBotName(botName); - else - c->Message(13, "You must name a valid bot."); - - if(tempBot) { - std::string stanceName; - BotStanceType botStance; - if (tempBot->GetBotOwner() != c) { - c->Message(13, "You must target a bot that you own."); - return; - } - - if(!strcasecmp(sep->arg[3], "list")) - botStance = tempBot->GetBotStance(); - else { - int stance = atoi(sep->arg[3]); - if(stance >= MaxStances || stance < 0){ - c->Message(0, "Usage #bot stance [name] [stance (id)] (Passive = 0, Balanced = 1, Efficient = 2, Reactive = 3, Aggressive = 4, Burn = 5, BurnAE = 6)"); - return; - } else { - botStance = (BotStanceType)stance; - if(botStance != tempBot->GetBotStance()) { - tempBot->SetBotStance(botStance); - tempBot->CalcChanceToCast(); - tempBot->Save(); - } - } - } - - switch(botStance) { - case BotStancePassive: { - stanceName = "Passive"; - break; - } - case BotStanceBalanced: { - stanceName = "Balanced"; - break; - } - case BotStanceEfficient: { - stanceName = "Efficient"; - break; - } - case BotStanceReactive: { - stanceName = "Reactive"; - break; - } - case BotStanceAggressive: { - stanceName = "Aggressive"; - break; - } - case BotStanceBurn: { - stanceName = "Burn"; - break; - } - case BotStanceBurnAE: { - stanceName = "BurnAE"; - break; - } - default: { - stanceName = "None"; - break; - } - } - c->Message(0, "Stance for %s: %s.", tempBot->GetCleanName(), stanceName.c_str()); - } - else - c->Message(13, "You must name a valid bot."); - } - else - c->Message(0, "Usage #bot stance [name] [stance (id)] (Passive = 0, Balanced = 1, Efficient = 2, Reactive = 3, Aggressive = 4, Burn = 5, BurnAE = 6)"); - - return; - } - - if(!strcasecmp(sep->arg[1], "defensive")) { - Bot* tempBot; - std::string botName = std::string(sep->arg[2]); - if(!botName.empty()) - tempBot = entity_list.GetBotByBotName(botName); - else { - c->Message(13, "You must name a valid bot."); - return; - } - - if(tempBot) { - uint8 botlevel = tempBot->GetLevel(); - uint32 defensiveSpellID = 0; - if (tempBot->GetBotOwner() != c) { - c->Message(13, "You must target a bot that you own."); - return; - } - - switch (tempBot->GetClass()) { - case WARRIOR: - if(botlevel >= 72) - defensiveSpellID = 10965; //Final Stand discipline - else if(botlevel >= 65) - defensiveSpellID = 4688; //Stonewall discipline - else if(botlevel >= 55) - defensiveSpellID = 4499; //Defensive discipline - else if(botlevel >= 52) - defensiveSpellID = 4503; //Evasive discipline - else - c->Message(0, "Warrior must be level 52 or higher."); - break; - case PALADIN: - if(botlevel >= 73) - defensiveSpellID = 11854; //Armor of Righteousness - else if(botlevel >= 69) - defensiveSpellID = 6663; //Guard of Righteousness - else if(botlevel >= 61) - defensiveSpellID = 6731; //Guard of Humility - else if(botlevel >= 56) - defensiveSpellID = 7004; //Guard of Piety - else - c->Message(0, "Paladin must be level 56 or higher."); - break; - case SHADOWKNIGHT: - if(botlevel >= 73) - defensiveSpellID = 11866; //Soul Carapace - else if(botlevel >= 69) - defensiveSpellID = 6673; //Soul shield - else if(botlevel >= 61) - defensiveSpellID = 6741; //Soul guard - else if(botlevel >= 56) - defensiveSpellID = 7005; //Ichor guard - else - c->Message(0, "Shadow Knight must be level 56 or higher."); - break; - default: - c->Message(0, "You must select a Warrior, Paladin, or Shadow Knight."); - break; - } - - if(defensiveSpellID > 0) - tempBot->UseDiscipline(defensiveSpellID, tempBot->GetID()); - } - else - c->Message(13, "You must name a valid bot."); - - return; - } - - // #bot healrotation ... - if(!strcasecmp(sep->arg[1], "healrotation")) { - if(!strcasecmp(sep->arg[2], "help")) { - c->Message(0, "#bot healrotation help - will show this help."); - c->Message(0, "#bot healrotation create [target]. This will create a heal rotation with the designated leader."); - c->Message(0, "#bot healrotation addmember "); - c->Message(0, "#bot healrotation removemember "); - c->Message(0, "#bot healrotation addtarget [bot healrotation target name to add] "); - c->Message(0, "#bot healrotation removetarget "); - c->Message(0, "#bot healrotation cleartargets "); - c->Message(0, "#bot healrotation fastheals "); - c->Message(0, "#bot healrotation start "); - c->Message(0, "#bot healrotation stop "); - c->Message(0, "#bot healrotation list "); - return; - } - - if(!strcasecmp(sep->arg[2], "create")) { - if(sep->argnum == 5 || sep->argnum == 6) { - Bot* leaderBot; - std::string botName = std::string(sep->arg[3]); - if(!botName.empty()) - leaderBot = entity_list.GetBotByBotName(botName); - else { - c->Message(13, "You must name a valid heal rotation leader."); - return; - } - - if(leaderBot) { - Mob* target = nullptr; - uint32 timer; - bool fastHeals = false; - if (!sep->IsNumber(4)) { - c->Message(0, "Usage #bot healrotation create [target]."); - return; - } - - timer = (uint32)(atof(sep->arg[4]) * 1000); - if (leaderBot->GetBotOwner() != c) { - c->Message(13, "You must target a bot that you own."); - return; - } - - if (!(leaderBot->IsBotCaster() && leaderBot->CanHeal())) { - c->Message(13, "Heal rotation members must be able to heal."); - return; - } - - if(!strcasecmp(sep->arg[5], "fasthealson")) - fastHeals = true; - else if(strcasecmp(sep->arg[5], "fasthealsoff")) { - c->Message(0, "Usage #bot healrotation create [target]."); - return; - } - - if(!leaderBot->GetInHealRotation()) { - if(sep->argnum == 6) { - std::string targetName = std::string(sep->arg[6]); - if(!targetName.empty()) - target = entity_list.GetMob(targetName.c_str()); - else { - c->Message(13, "You must name a valid target."); - return; - } - - if(!target) { - c->Message(13, "You must name a valid target."); - return; - } - } - leaderBot->CreateHealRotation(target, timer); - leaderBot->SetHealRotationUseFastHeals(fastHeals); - c->Message(0, "Bot heal rotation created successfully."); - } else { - c->Message(13, "That bot is already in a heal rotation."); - return; - } - } else { - c->Message(13, "You must name a valid bot."); - return; - } - } else { - c->Message(0, "Usage #bot healrotation create [target]."); - return; - } - } - - if(!strcasecmp(sep->arg[2], "addmember")) { - if(sep->argnum == 4) { - Bot* leaderBot; - std::string botName = std::string(sep->arg[3]); - if(!botName.empty()) - leaderBot = entity_list.GetBotByBotName(botName); - else { - c->Message(13, "You must name a valid bot."); - return; - } - - if(leaderBot) { - Bot* healer; - std::string healerName = std::string(sep->arg[4]); - if (leaderBot->GetBotOwner() != c) { - c->Message(13, "You must target a bot that you own."); - return; - } - - if(!healerName.empty()) - healer = entity_list.GetBotByBotName(healerName); - else { - c->Message(13, "You must name a valid bot."); - return; - } - - if(healer) { - if (healer->GetBotOwner() != c) { - c->Message(13, "You must target a bot that you own."); - return; - } - - if (!(healer->IsBotCaster() && healer->CanHeal())) { - c->Message(13, "Heal rotation members must be able to heal."); - return; - } - - if(leaderBot->AddHealRotationMember(healer)) - c->Message(0, "Bot heal rotation member added successfully."); - else - c->Message(13, "Unable to add bot to rotation."); - } - } else { - c->Message(13, "You must name a valid bot."); - return; - } - } else { - c->Message(0, "#bot healrotation addmember "); - return; - } - } - - if(!strcasecmp(sep->arg[2], "removemember")) { - if(sep->argnum == 4) { - Bot* leaderBot; - std::string botName = std::string(sep->arg[3]); - - if(!botName.empty()) - leaderBot = entity_list.GetBotByBotName(botName); - else { - c->Message(13, "You must name a valid bot."); - return; - } - - if(leaderBot) { - if (leaderBot->GetBotOwner() != c) { - c->Message(13, "You must target a bot that you own."); - return; - } - - Bot* healer; - std::string healerName = std::string(sep->arg[4]); - if(!healerName.empty()) - healer = entity_list.GetBotByBotName(healerName); - else { - c->Message(13, "You must name a valid bot."); - return; - } - - if(healer) { - if (healer->GetBotOwner() != c) { - c->Message(13, "You must target a bot that you own."); - return; - } - - if(leaderBot->RemoveHealRotationMember(healer)) - c->Message(0, "Bot heal rotation member removed successfully."); - else - c->Message(13, "Unable to remove bot from rotation."); - } else { - c->Message(13, "You must name a valid bot."); - return; - } - } else { - c->Message(13, "You must name a valid bot."); - return; - } - } else { - c->Message(0, "#bot healrotation removemember "); - return; - } - } - - if(!strcasecmp(sep->arg[2], "addtarget")) { - if(sep->argnum == 3 || sep->argnum == 4) { - Bot* leaderBot; - std::string botName = std::string(sep->arg[3]); - if(!botName.empty()) - leaderBot = entity_list.GetBotByBotName(botName); - else { - c->Message(13, "You must name a valid heal rotation leader."); - return; - } - - if(leaderBot) { - if (leaderBot->GetBotOwner() != c) { - c->Message(13, "You must target a bot that you own."); - return; - } - - Mob* target = nullptr; - std::string targetName = std::string(sep->arg[4]); - if(!targetName.empty()) - target = entity_list.GetMob(targetName.c_str()); - else { - if(c->GetTarget() != nullptr) - target = c->GetTarget(); - } - - if(target) { - if(leaderBot->AddHealRotationTarget(target)) - c->Message(0, "Bot heal rotation target added successfully."); - else - c->Message(13, "Unable to add rotation target."); - } else { - c->Message(13, "Invalid target."); - return; - } - } else { - c->Message(13, "You must name a valid bot."); - return; - } - } else { - c->Message(0, "#bot healrotation addtarget [bot healrotation target name to add] "); - return; - } - } - - if(!strcasecmp(sep->arg[2], "removetarget")) { - if(sep->argnum == 4) { - Bot* leaderBot; - std::string botName = std::string(sep->arg[3]); - if(!botName.empty()) - leaderBot = entity_list.GetBotByBotName(botName); - else { - c->Message(13, "You must name a valid heal rotation leader."); - return; - } - - if(leaderBot) { - if (leaderBot->GetBotOwner() != c) { - c->Message(13, "You must target a bot that you own."); - return; - } - - Mob* target; - std::string targetName = std::string(sep->arg[4]); - if(!targetName.empty()) - target = entity_list.GetMob(targetName.c_str()); - else { - c->Message(13, "You must name a valid target."); - return; - } - - if(target) { - if(leaderBot->RemoveHealRotationTarget(target)) - c->Message(0, "Bot heal rotation target removed successfully."); - else - c->Message(13, "Unable to remove rotation target."); - } - } else { - c->Message(13, "You must name a valid bot."); - return; - } - } else { - c->Message(0, "#bot healrotation removetarget "); - return; - } - } - - if(!strcasecmp(sep->arg[2], "start")) { - if(sep->argnum == 3) { - if(!strcasecmp(sep->arg[3], "all")) { - std::list BotList = entity_list.GetBotsByBotOwnerCharacterID(c->CharacterID()); - for(std::list::iterator botListItr = BotList.begin(); botListItr != BotList.end(); ++botListItr) { - Bot* leaderBot = *botListItr; - if(leaderBot->GetInHealRotation() && leaderBot->GetHealRotationLeader() == leaderBot) { - std::list rotationMemberList; - int index = 0; - rotationMemberList = GetBotsInHealRotation(leaderBot); - for(std::list::iterator rotationMemberItr = rotationMemberList.begin(); rotationMemberItr != rotationMemberList.end(); ++rotationMemberItr) { - Bot* tempBot = *rotationMemberItr; - if(tempBot) { - tempBot->SetHealRotationActive(true); - tempBot->SetHealRotationNextHealTime(Timer::GetCurrentTime() + index * leaderBot->GetHealRotationTimer() * 1000); - tempBot->SetHasHealedThisCycle(false); - } - index++; - } - c->Message(0, "Bot heal rotation started successfully."); - } - } - } else { - Bot* leaderBot; - std::string botName = std::string(sep->arg[3]); - if(!botName.empty()) - leaderBot = entity_list.GetBotByBotName(botName); - else { - c->Message(13, "You must name a valid heal rotation leader."); - return; - } - - if(leaderBot) { - std::list botList; - int index = 0; - if (leaderBot->GetBotOwner() != c) { - c->Message(13, "You must target a bot that you own."); - return; - } - - botList = GetBotsInHealRotation(leaderBot); - for(std::list::iterator botListItr = botList.begin(); botListItr != botList.end(); ++botListItr) { - Bot* tempBot = *botListItr; - if(tempBot) { - tempBot->SetHealRotationActive(true); - tempBot->SetHealRotationNextHealTime(Timer::GetCurrentTime() + index * leaderBot->GetHealRotationTimer() * 1000); - tempBot->SetHasHealedThisCycle(false); - } - index++; - } - c->Message(0, "Bot heal rotation started successfully."); - } else { - c->Message(13, "You must name a valid bot."); - return; - } - } - } else { - c->Message(0, "#bot healrotation start "); - return; - } - } - - if(!strcasecmp(sep->arg[2], "stop")) { - if(sep->argnum == 3) { - if(!strcasecmp(sep->arg[3], "all")) { - std::list BotList = entity_list.GetBotsByBotOwnerCharacterID(c->CharacterID()); - for(std::list::iterator botListItr = BotList.begin(); botListItr != BotList.end(); ++botListItr) { - Bot* leaderBot = *botListItr; - if(leaderBot->GetInHealRotation() && leaderBot->GetHealRotationLeader() == leaderBot) { - std::list rotationMemberList; - rotationMemberList = GetBotsInHealRotation(leaderBot); - for(std::list::iterator rotationMemberItr = rotationMemberList.begin(); rotationMemberItr != rotationMemberList.end(); ++rotationMemberItr) { - Bot* tempBot = *rotationMemberItr; - if(tempBot) { - tempBot->SetHealRotationActive(false); - tempBot->SetHasHealedThisCycle(false); - } - } - c->Message(0, "Bot heal rotation started successfully."); - } - } - } else { - Bot* leaderBot; - std::string botName = std::string(sep->arg[3]); - if(!botName.empty()) - leaderBot = entity_list.GetBotByBotName(botName); - else { - c->Message(13, "You must name a valid heal rotation leader."); - return; - } - - if(leaderBot) { - std::list botList; - if (leaderBot->GetBotOwner() != c) { - c->Message(13, "You must target a bot that you own."); - return; - } - - botList = GetBotsInHealRotation(leaderBot); - for(std::list::iterator botListItr = botList.begin(); botListItr != botList.end(); ++botListItr) { - Bot* tempBot = *botListItr; - if(tempBot && tempBot->GetBotOwnerCharacterID() == c->CharacterID()) { - tempBot->SetHealRotationActive(false); - tempBot->SetHasHealedThisCycle(false); - } - } - - c->Message(0, "Bot heal rotation stopped successfully."); - } else { - c->Message(13, "You must name a valid bot."); - return; - } - } - } else { - c->Message(0, "#bot healrotation stop "); - return; - } - } - - if(!strcasecmp(sep->arg[2], "list")) { - if(sep->argnum == 3) { - bool showAll = false; - Bot* leaderBot; - std::string botName = std::string(sep->arg[3]); - if(!strcasecmp(sep->arg[3], "all")) { - std::list BotList = entity_list.GetBotsByBotOwnerCharacterID(c->CharacterID()); - for(std::list::iterator botListItr = BotList.begin(); botListItr != BotList.end(); ++botListItr) { - Bot* tempBot = *botListItr; - if(tempBot->GetInHealRotation() && tempBot->GetHealRotationLeader() == tempBot) - c->Message(0, "Bot Heal Rotation- Leader: %s, Number of Members: %i, Timer: %1.1f", tempBot->GetCleanName(), tempBot->GetNumHealRotationMembers(), (float)(tempBot->GetHealRotationTimer()/1000)); - } - } else { - std::string botName = std::string(sep->arg[3]); - if(!botName.empty()) - leaderBot = entity_list.GetBotByBotName(botName); - else { - c->Message(13, "You must name a valid heal rotation leader."); - return; - } - - if(leaderBot) { - std::list botList; - if (leaderBot->GetBotOwner() != c) { - c->Message(13, "You must target a bot that you own."); - return; - } - - botList = GetBotsInHealRotation(leaderBot); - c->Message(0, "Bot Heal Rotation- Leader: %s", leaderBot->GetCleanName()); - c->Message(0, "Bot Heal Rotation- Timer: %1.1f", ((float)leaderBot->GetHealRotationTimer()/1000.0f)); - for(std::list::iterator botListItr = botList.begin(); botListItr != botList.end(); ++botListItr) { - Bot* tempBot = *botListItr; - if(tempBot && tempBot->GetBotOwnerCharacterID() == c->CharacterID()) - c->Message(0, "Bot Heal Rotation- Member: %s", tempBot->GetCleanName()); - } - - for(int i = 0; i < MaxHealRotationTargets; i++) { - if(leaderBot->GetHealRotationTarget(i)) { - Mob* tempTarget = leaderBot->GetHealRotationTarget(i); - if(tempTarget) { - std::string targetInfo = ""; - targetInfo += tempTarget->GetHPRatio() < 0 ? "(dead) " : ""; - targetInfo += tempTarget->GetZoneID() != leaderBot->GetZoneID() ? "(not in zone) " : ""; - c->Message(0, "Bot Heal Rotation- Target: %s %s", tempTarget->GetCleanName(), targetInfo.c_str()); - } - } - } - } else { - c->Message(13, "You must name a valid bot."); - return; - } - } - } else { - c->Message(0, "#bot healrotation list "); - return; - } - } - - if(!strcasecmp(sep->arg[2], "cleartargets")) { - if(sep->argnum == 3) { - Bot* leaderBot; - std::string botName = std::string(sep->arg[3]); - if(!botName.empty()) - leaderBot = entity_list.GetBotByBotName(botName); - else { - c->Message(13, "You must name a valid heal rotation leader."); - return; - } - - if(leaderBot) { - std::list botList; - if (leaderBot->GetBotOwner() != c) { - c->Message(13, "You must target a bot that you own."); - return; - } - - botList = GetBotsInHealRotation(leaderBot); - for(std::list::iterator botListItr = botList.begin(); botListItr != botList.end(); ++botListItr) { - Bot* tempBot = *botListItr; - if(tempBot && tempBot->GetBotOwnerCharacterID() == c->CharacterID()) - tempBot->ClearHealRotationTargets(); - } - } else { - c->Message(13, "You must name a valid bot."); - return; - } - } else { - c->Message(0, "#bot healrotation cleartargets "); - return; - } - } - - if(!strcasecmp(sep->arg[2], "fastheals")) { - if(sep->argnum == 3) { - Bot* leaderBot; - std::string botName = std::string(sep->arg[3]); - if(!botName.empty()) - leaderBot = entity_list.GetBotByBotName(botName); - else { - c->Message(13, "You must name a valid heal rotation leader."); - return; - } - - if(leaderBot) { - bool fastHeals = false; - std::list botList; - if (leaderBot->GetBotOwner() != c) { - c->Message(13, "You must target a bot that you own."); - return; - } - - if(!strcasecmp(sep->arg[4], "on")) - fastHeals = true; - else if(strcasecmp(sep->arg[4], "off")) { - c->Message(0, "Usage #bot healrotation fastheals ."); - return; - } - - botList = GetBotsInHealRotation(leaderBot); - for(std::list::iterator botListItr = botList.begin(); botListItr != botList.end(); ++botListItr) { - Bot* tempBot = *botListItr; - if(tempBot && tempBot->GetBotOwnerCharacterID() == c->CharacterID()) - tempBot->SetHealRotationUseFastHeals(fastHeals); - } - } else { - c->Message(13, "You must name a valid bot."); - return; - } - } else { - c->Message(0, "#bot healrotation fastheals "); - return; - } - } - } - - if(!strcasecmp(sep->arg[1], "setinspectmessage")) { - if(!strcasecmp(sep->arg[2], "help")) { - c->Message(0, "[Titanium clients:]"); - c->Message(0, "- Self-inspect and type your bot's inspect message"); - c->Message(0, "- Close the self-inspect window"); - c->Message(0, "- Self-inspect again to update the server"); - c->Message(0, "- Target a bot that you own and wish to update"); - c->Message(0, "- type #bot setinspectmessage to set the bot's message"); - c->Message(0, "[Secrets of Faydwer and higher clients:]"); - c->Message(0, "- Self-inspect and type your bot's inspect message"); - c->Message(0, "- Close the self-inspect window to update the server"); - c->Message(0, "- Target a bot that you own and wish to update"); - c->Message(0, "- type #bot setinspectmessage to set the bot's message"); - } else { - Mob *target = c->GetTarget(); - if(target && target->IsBot() && (c == target->GetOwner()->CastToClient())) { - const InspectMessage_Struct& playermessage = c->GetInspectMessage(); - InspectMessage_Struct& botmessage = target->CastToBot()->GetInspectMessage(); - memcpy(&botmessage, &playermessage, sizeof(InspectMessage_Struct)); - database.SetBotInspectMessage(target->CastToBot()->GetBotID(), &botmessage); - c->Message(0, "Bot %s's inspect message now reflects your inspect message.", target->GetName()); - } else { - c->Message(0, "Your target must be a bot that you own."); - } - } - } - - if(!strcasecmp(sep->arg[1], "bardoutofcombat")) { - bool useOutOfCombatSongs = false; - if(sep->arg[2] && sep->arg[3]){ - if(!strcasecmp(sep->arg[2], "on")) - useOutOfCombatSongs = true; - else if (!strcasecmp(sep->arg[2], "off")) - useOutOfCombatSongs = false; - else { - c->Message(0, "Usage #bot bardoutofcombat [on|off]"); - return; - } - - Mob *target = c->GetTarget(); - if(target && target->IsBot() && (c == target->GetOwner()->CastToClient())) { - Bot* bardBot = target->CastToBot(); - if(bardBot) { - bardBot->SetBardUseOutOfCombatSongs(useOutOfCombatSongs); - c->Message(0, "Bard use of out of combat songs updated."); - } - } else - c->Message(0, "Your target must be a bot that you own."); - } else - c->Message(0, "Usage #bot bardoutofcombat [on|off]"); - return; - } - - if(!strcasecmp(sep->arg[1], "showhelm")) { - bool showhelm = true; - if (sep->arg[2]) { - if (!strcasecmp(sep->arg[2], "on")) - showhelm = true; - else if (!strcasecmp(sep->arg[2], "off")) - showhelm = false; - else { - c->Message(0, "Usage #bot showhelm [on|off]"); - return; - } - - Mob *target = c->GetTarget(); - if (target && target->IsBot() && (c == target->GetOwner()->CastToClient())) { - Bot* b = target->CastToBot(); - if (b) { - b->SetShowHelm(showhelm); - EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); - SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; - /* - [10-16-2015 :: 14:58:02] [Packet :: Client -> Server (Dump)] [OP_SpawnAppearance - 0x01d1] [Size: 10] - 0: A4 02 [2B 00] 00 00 00 00 - showhelm = false - [10-16-2015 :: 14:57:56] [Packet :: Client -> Server (Dump)] [OP_SpawnAppearance - 0x01d1] [Size: 10] - 0: A4 02 [2B 00] 01 00 00 00 - showhelm = true - */ - sa_out->spawn_id = b->GetID(); - sa_out->type = AT_ShowHelm; // value = 43 (0x002B) - sa_out->parameter = (showhelm ? 1 : 0); - entity_list.QueueClients(b, outapp, true); - safe_delete(outapp); - c->Message(0, "Your bot will %s show their helmet.", (showhelm ? "now" : "no longer")); - } - } - } else - c->Message(0, "Usage #bot showhelm [on|off]"); - - return; - } -} - bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, float iRange, uint16 iSpellTypes) { if((iSpellTypes&SpellTypes_Detrimental) != 0) { Log.Out(Logs::General, Logs::Error, "Error: detrimental spells requested from AICheckCloseBeneficialSpells!!"); @@ -13044,37 +8848,6 @@ std::list EntityList::GetBotsByBotOwnerCharacterID(uint32 botOwnerCharacte return Result; } -void EntityList::BotPickLock(Bot* rogue) { - for (auto it = door_list.begin(); it != door_list.end(); ++it) { - Doors *cdoor = it->second; - if(!cdoor || cdoor->IsDoorOpen()) - continue; - - auto diff = (rogue->GetPosition() - cdoor->GetPosition()); - float curdist = ((diff.x * diff.x) + (diff.y * diff.y)); - if(((diff.z * diff.z) >= 10) || curdist > 130) - continue; - - const ItemInst* item1 = rogue->GetBotItem(MainHands); - const ItemInst* item2 = rogue->GetBotItem(MainPrimary); - float bonus1 = 0.0f; - float bonus2 = 0.0f; - float skill = rogue->GetSkill(SkillPickLock); - if(item1) - if(item1->GetItem()->SkillModType == SkillPickLock) - bonus1 = (skill * (((float)item1->GetItem()->SkillModValue) / 100.0f)); - - if(item2) - if(item2->GetItem()->SkillModType == SkillPickLock) - bonus2 = (skill * (((float)item2->GetItem()->SkillModValue) / 100.0f)); - - if((skill + bonus1 + bonus2) >= cdoor->GetLockpick()) - cdoor->ForceOpen(rogue); - else - rogue->BotGroupSay(rogue, "I am not skilled enough for this lock."); - } -} - bool EntityList::RemoveBot(uint16 entityID) { bool Result = false; if(entityID > 0) { @@ -13222,7 +8995,8 @@ uint8 Bot::GetNumberNeedingHealedInGroup(uint8 hpr, bool includePets) { return needHealed; } -uint32 Bot::GetEquipmentColor(uint8 material_slot) const { +uint32 Bot::GetEquipmentColor(uint8 material_slot) const +{ int16 slotid = 0; uint32 botid = this->GetBotID(); slotid = Inventory::CalcSlotFromMaterial(material_slot); @@ -13460,288 +9234,213 @@ bool Bot::UseDiscipline(uint32 spell_id, uint32 target) { return true; } -void Bot::CreateHealRotation( Mob* target, uint32 timer ) { - SetInHealRotation(true); - SetHealRotationActive(false); - SetNumHealRotationMembers(GetNumHealRotationMembers()+1); - SetHealRotationLeader(this); - SetNextHealRotationMember(this); - SetPrevHealRotationMember(this); - SetHealRotationTimer(timer); - SetHasHealedThisCycle(false); - if(target) - AddHealRotationTarget(target); +// new healrotation code +bool Bot::CreateHealRotation(uint32 interval_ms, bool fast_heals, bool adaptive_targeting, bool casting_override) +{ + if (IsHealRotationMember()) + return false; + if (!HealRotation::IsMemberClass(GetClass())) + return false; + + m_member_of_heal_rotation = std::make_shared(this, interval_ms, fast_heals, adaptive_targeting, casting_override); + + return IsHealRotationMember(); } -bool Bot::AddHealRotationMember( Bot* healer ) { - if(healer) { - if(GetNumHealRotationMembers() > 0 && GetNumHealRotationMembers() < MaxHealRotationMembers) { - Bot* tempBot = GetPrevHealRotationMember(); - if(tempBot) { - for(int i = 0; i < 3; i++){ - healer->ClearHealRotationMembers(); - healer->ClearHealRotationTargets(); - healer->AddHealRotationTarget(entity_list.GetMob(_healRotationTargets[i])); - } - healer->SetHealRotationTimer(tempBot->GetHealRotationTimer()); - healer->SetHealRotationLeader(this); - healer->SetNextHealRotationMember(this); - healer->SetPrevHealRotationMember(tempBot); - healer->SetInHealRotation(true); - healer->SetHasHealedThisCycle(false); - healer->SetHealRotationUseFastHeals(tempBot->GetHealRotationUseFastHeals()); - tempBot->SetNextHealRotationMember(healer); - SetPrevHealRotationMember(healer); - std::list botList = GetBotsInHealRotation(this); - for(std::list::iterator botListItr = botList.begin(); botListItr != botList.end(); ++botListItr) { - Bot* tempBot = *botListItr; - if(tempBot) - tempBot->SetNumHealRotationMembers(GetNumHealRotationMembers()+1); - } - return true; - } - } - } - return false; +bool Bot::DestroyHealRotation() +{ + if (!IsHealRotationMember()) + return true; + + m_member_of_heal_rotation->ClearTargetPool(); + m_member_of_heal_rotation->ClearMemberPool(); + + return !IsHealRotationMember(); } -bool Bot::RemoveHealRotationMember( Bot* healer ) { - if(healer && GetNumHealRotationMembers() > 0) { - Bot* leader = healer->GetHealRotationLeader(); - Bot* prevBot = healer->GetPrevHealRotationMember(); - Bot* nextBot = healer->GetNextHealRotationMember(); - if(healer == this) { - if(nextBot != this) - leader = nextBot; - } +bool Bot::JoinHealRotationMemberPool(std::shared_ptr* heal_rotation) +{ + if (IsHealRotationMember()) + return false; + if (!heal_rotation->use_count()) + return false; + if (!(*heal_rotation)) + return false; + if (!HealRotation::IsMemberClass(GetClass())) + return false; - healer->SetHealRotationTimer(0); - healer->ClearHealRotationMembers(); - healer->ClearHealRotationTargets(); - healer->ClearHealRotationLeader(); - healer->SetHasHealedThisCycle(false); - healer->SetHealRotationActive(false); - healer->SetInHealRotation(false); - if(prevBot && nextBot && GetNumHealRotationMembers() > 1) { - prevBot->SetNextHealRotationMember(nextBot); - nextBot->SetPrevHealRotationMember(prevBot); - } + if (!(*heal_rotation)->AddMemberToPool(this)) + return false; - std::list botList = GetBotsInHealRotation(leader); - for(std::list::iterator botListItr = botList.begin(); botListItr != botList.end(); ++botListItr) { - Bot* tempBot = *botListItr; - if(tempBot) { - tempBot->SetNumHealRotationMembers(GetNumHealRotationMembers() - 1); - if(tempBot->GetHealRotationLeader() != leader) - tempBot->SetHealRotationLeader(leader); - } - } + m_member_of_heal_rotation = *heal_rotation; + + return true; +} + +bool Bot::LeaveHealRotationMemberPool() +{ + if (!IsHealRotationMember()) { + m_member_of_heal_rotation.reset(); return true; } - return false; + + m_member_of_heal_rotation->RemoveMemberFromPool(this); + m_member_of_heal_rotation.reset(); + + return !IsHealRotationMember(); } -void Bot::SetHealRotationLeader( Bot* leader ) { - _healRotationLeader = leader->GetBotID(); +bool Bot::UseHealRotationFastHeals() +{ + if (!IsHealRotationMember()) + return false; + + return m_member_of_heal_rotation->FastHeals(); } -void Bot::SetNextHealRotationMember( Bot* healer ) { - _healRotationMemberNext = healer->GetBotID(); +bool Bot::UseHealRotationAdaptiveTargeting() +{ + if (!IsHealRotationMember()) + return false; + + return m_member_of_heal_rotation->AdaptiveTargeting(); } -void Bot::SetPrevHealRotationMember( Bot* healer ) { - _healRotationMemberPrev = healer->GetBotID(); +bool Bot::IsHealRotationActive() +{ + if (!IsHealRotationMember()) + return false; + + return m_member_of_heal_rotation->IsActive(); } -Bot* Bot::GetHealRotationLeader( ) { - if(_healRotationLeader) - return entity_list.GetBotByBotID(_healRotationLeader); +bool Bot::IsHealRotationReady() +{ + if (!IsHealRotationMember()) + return false; - return 0; + return m_member_of_heal_rotation->CastingReady(); } -Bot* Bot::GetNextHealRotationMember( ) { - if(_healRotationMemberNext) - return entity_list.GetBotByBotID(_healRotationMemberNext); +bool Bot::IsHealRotationCaster() +{ + if (!IsHealRotationMember()) + return false; - return 0; + return (m_member_of_heal_rotation->CastingMember() == this); } -Bot* Bot::GetPrevHealRotationMember( ) { - if(_healRotationMemberNext) - return entity_list.GetBotByBotID(_healRotationMemberPrev); +bool Bot::HealRotationPokeTarget() +{ + if (!IsHealRotationMember()) + return false; - return 0; + return m_member_of_heal_rotation->PokeCastingTarget(); } -bool Bot::AddHealRotationTarget( Mob* target ) { - if(target) { - for (int i = 0; i < MaxHealRotationTargets; ++i) { - if(_healRotationTargets[i] > 0) { - Mob* tempTarget = entity_list.GetMob(_healRotationTargets[i]); - if(!tempTarget) { - _healRotationTargets[i] = 0; - } else if(!strcasecmp(tempTarget->GetCleanName(), target->GetCleanName())) { - if(tempTarget->GetID() != target->GetID()) - _healRotationTargets[i] = target->GetID(); +Mob* Bot::HealRotationTarget() +{ + if (!IsHealRotationMember()) + return nullptr; - return false; - } - } + return m_member_of_heal_rotation->CastingTarget(); +} - if (_healRotationTargets[i] == 0) { - std::list botList = GetBotsInHealRotation(this); - _healRotationTargets[i] = target->GetID(); - for(std::list::iterator botListItr = botList.begin(); botListItr != botList.end(); ++botListItr) { - Bot* tempBot = *botListItr; - if(tempBot && tempBot != this) - tempBot->AddHealRotationTarget(target, i); - } - return true; - } +bool Bot::AdvanceHealRotation(bool use_interval) +{ + if (!IsHealRotationMember()) + return false; + + return m_member_of_heal_rotation->AdvanceRotation(use_interval); +} + +bool Bot::IsMyHealRotationSet() +{ + if (!IsHealRotationMember()) + return false; + if (!m_member_of_heal_rotation->IsActive()) + return false; + if (!m_member_of_heal_rotation->CastingReady()) + return false; + if (m_member_of_heal_rotation->CastingMember() != this) + return false; + if (!m_member_of_heal_rotation->PokeCastingTarget()) + return false; + + return true; +} + +bool Bot::AmICastingForHealRotation() +{ + if (!IsHealRotationMember()) + return false; + + return m_member_of_heal_rotation->MemberIsCasting(this); +} + +void Bot::SetMyCastingForHealRotation(bool flag) +{ + if (!IsHealRotationMember()) + return; + + m_member_of_heal_rotation->SetMemberIsCasting(this, flag); +} + +bool Bot::DyeArmor(int16 slot_id, uint32 rgb, bool all_flag, bool save_flag) +{ + if (all_flag) { + if (slot_id != INVALID_INDEX) + return false; + + for (uint8 i = 0; i < MaterialPrimary; ++i) { + uint8 inv_slot = Inventory::CalcSlotFromMaterial(i); + ItemInst* inst = m_inv.GetItem(inv_slot); + if (!inst) + continue; + + inst->SetColor(rgb); + SendWearChange(i); } } - return false; -} + else { + uint8 mat_slot = Inventory::CalcMaterialFromSlot(slot_id); + if (mat_slot == _MaterialInvalid || mat_slot >= MaterialPrimary) + return false; -bool Bot::AddHealRotationTarget( Mob *target, int index ) { - if (target && index < MaxHealRotationTargets) { - _healRotationTargets[index] = target->GetID(); - return true; + ItemInst* inst = m_inv.GetItem(slot_id); + if (!inst) + return false; + + inst->SetColor(rgb); + SendWearChange(mat_slot); } - return false; -} -bool Bot::RemoveHealRotationTarget( Mob* target ) { - int index = 0; - bool removed = false; - if(target) { - for(int i = 0; i < MaxHealRotationTargets; i++){ - if(_healRotationTargets[i] == target->GetID()) { - std::list botList = GetBotsInHealRotation(this); - _healRotationTargets[i] = 0; - index = i; - removed = true; - for(std::list::iterator botListItr = botList.begin(); botListItr != botList.end(); ++botListItr) { - Bot* tempBot = *botListItr; - if(tempBot) - tempBot->RemoveHealRotationTarget(i); - } - } - } - } - return removed; -} + if (save_flag) { + std::string where_clause; + if (all_flag) + where_clause = StringFormat(" WHERE `slot_id` IN ('%u', '%u', '%u', '%u', '%u', '%u', '%u')", + MainHead, MainArms, MainWrist1, MainHands, MainChest, MainLegs, MainFeet); + else + where_clause = StringFormat(" WHERE `slot_id` = '%u'", slot_id); -bool Bot::RemoveHealRotationTarget( int index ) { - if(index >= 0) { - _healRotationTargets[index] = 0; - if(index < MaxHealRotationTargets) { - for(int i = index; i < MaxHealRotationTargets; i++){ - _healRotationTargets[i] = _healRotationTargets[i + 1]; - _healRotationTargets[i + 1] = 0; - } - return true; - } - } - return false; -} + std::string query = StringFormat( + "UPDATE `bot_inventories`" + " SET `inst_color` = '%u'" + " %s" + " AND `bot_id` = '%u'", + rgb, + where_clause.c_str(), + GetBotID() + ); -void Bot::ClearHealRotationMembers() { - _healRotationMemberPrev = 0; - _healRotationMemberNext = 0; -} - -void Bot::ClearHealRotationTargets() { - for(int i = 0; i < MaxHealRotationTargets; i++) { - _healRotationTargets[i] = 0; - } -} - -Mob* Bot::GetHealRotationTarget( ) { - Mob* tank = nullptr; - Mob* first = nullptr; - Mob* target = nullptr; - int removeIndex = 0; - int count = 0; - for(int i = 0; i < MaxHealRotationTargets; i++) { - if(_healRotationTargets[i] > 0) { - target = entity_list.GetMob(_healRotationTargets[i]); - if(target) { - if(target->GetZoneID() == GetZoneID() && !(target->GetAppearance() == eaDead && !(target->IsClient() && target->CastToClient()->GetFeigned()))) { - count++; - if(!first) - first = target; - - if(!tank) { - Group* g = target->GetGroup(); - if(g && !strcasecmp(g->GetMainTankName(), target->GetCleanName())) - tank = target; - } - } - } else { - if(removeIndex == 0) - removeIndex = i; - } + auto results = database.QueryDatabase(query); + if (!results.Success() && GetOwner() && GetOwner()->IsClient()) { + GetOwner()->CastToClient()->Message(15, "Failed to save dye armor changes for %s due to unknown cause", GetCleanName()); + return false; } } - if (removeIndex > 0) - RemoveHealRotationTarget(removeIndex); - - if(tank) - return tank; - - return first; -} - -Mob* Bot::GetHealRotationTarget( uint8 index ) { - Mob* target = nullptr; - if(_healRotationTargets[index] > 0) - target = entity_list.GetMob(_healRotationTargets[index]); - - return target; -} - -std::list Bot::GetBotsInHealRotation(Bot* rotationLeader) { - std::list Result; - if(rotationLeader != nullptr) { - Result.push_back(rotationLeader); - Bot* rotationMember = rotationLeader->GetNextHealRotationMember(); - while(rotationMember && rotationMember != rotationLeader) { - Result.push_back(rotationMember); - rotationMember = rotationMember->GetNextHealRotationMember(); - } - } - return Result; -} - -void Bot::NotifyNextHealRotationMember(bool notifyNow) { - uint32 nextHealTime = notifyNow ? Timer::GetCurrentTime() : Timer::GetCurrentTime() + GetHealRotationTimer(); - Bot* nextMember = GetNextHealRotationMember(); - if(nextMember && nextMember != this) { - nextMember->SetHealRotationNextHealTime(nextHealTime); - nextMember->SetHasHealedThisCycle(false); - } -} - -void Bot::BotHealRotationsClear(Client* c) { - if(c) { - std::list BotList = entity_list.GetBotsByBotOwnerCharacterID(c->CharacterID()); - for(std::list::iterator botListItr = BotList.begin(); botListItr != BotList.end(); ++botListItr) { - Bot* tempBot = *botListItr; - if(tempBot->GetInHealRotation()) { - tempBot->SetInHealRotation(false); - tempBot->SetHealRotationActive(false); - tempBot->SetHasHealedThisCycle(false); - tempBot->SetHealRotationTimer(0); - tempBot->ClearHealRotationMembers(); - tempBot->ClearHealRotationTargets(); - tempBot->SetNumHealRotationMembers(0); - tempBot->ClearHealRotationLeader(); - } - } - } + return true; } std::string Bot::CreateSayLink(Client* c, const char* message, const char* name) { diff --git a/zone/bot.h b/zone/bot.h index c35fd1f7b..89759cd91 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -1,3 +1,21 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + #ifndef BOT_H #define BOT_H @@ -7,9 +25,11 @@ #include "mob.h" #include "client.h" #include "pets.h" +#include "heal_rotation.h" #include "groups.h" #include "corpse.h" #include "zonedb.h" +#include "bot_database.h" #include "string_ids.h" #include "../common/misc_functions.h" #include "../common/global_define.h" @@ -29,8 +49,6 @@ const int DisciplineReuseStart = MaxSpellTimer + 1; const int MaxTimer = MaxSpellTimer + MaxDisciplineTimer; const int MaxStances = 7; const int MaxSpellTypes = 16; -const int MaxHealRotationMembers = 6; -const int MaxHealRotationTargets = 3; enum BotStanceType { BotStancePassive, @@ -39,9 +57,58 @@ enum BotStanceType { BotStanceReactive, BotStanceAggressive, BotStanceBurn, - BotStanceBurnAE + BotStanceBurnAE, + BotStanceUnknown }; +#define BOT_STANCE_COUNT 8 +#define VALIDBOTSTANCE(x) ((x >= (int)BotStancePassive && x <= (int)BotStanceBurnAE) ? ((BotStanceType)x) : (BotStanceUnknown)) + +static const std::string bot_stance_name[BOT_STANCE_COUNT] = { + "Passive", // 0 + "Balanced", // 1 + "Efficient", // 2 + "Reactive", // 3 + "Aggressive", // 4 + "Burn", // 5 + "BurnAE", // 6 + "Unknown" // 7 +}; + +static const char* GetBotStanceName(int stance_id) { return bot_stance_name[VALIDBOTSTANCE(stance_id)].c_str(); } + +#define VALIDBOTEQUIPSLOT(x) ((x >= EmuConstants::EQUIPMENT_BEGIN && x <= EmuConstants::EQUIPMENT_END) ? (x) : ((x == MainPowerSource) ? (22) : (23))) + +static std::string bot_equip_slot_name[EmuConstants::EQUIPMENT_SIZE + 2] = +{ + "Charm", // MainCharm + "Left Ear", // MainEar1 + "Head", // MainHead + "Face", // MainFace + "Right Ear", // MainEar2 + "Neck", // MainNeck + "Shoulders", // MainShoulders + "Arms", // MainArms + "Back", // MainBack + "Left Wrist", // MainWrist1 + "Right Wrist", // MainWrist2 + "Range", // MainRange + "Hands", // MainHands + "Primary Hand", // MainPrimary + "Secondary Hand", // MainSecondary + "Left Finger", // MainFinger1 + "Right Finger", // MainFinger2 + "Chest", // MainChest + "Legs", // MainLegs + "Feet", // MainFeet + "Waist", // MainWaist + "Ammo", // MainAmmo + "Power Source", // 22 (MainPowerSource = 9999) + "Unknown" +}; + +static const char* GetBotEquipSlotName(int slot_id) { return bot_equip_slot_name[VALIDBOTEQUIPSLOT(slot_id)].c_str(); } + enum SpellTypeIndex { SpellType_NukeIndex, SpellType_HealIndex, @@ -110,7 +177,7 @@ public: BotRoleRaidHealer }; - enum EqExpansions { + enum EqExpansions { // expansions are off..EQ should be '0' ExpansionNone, ExpansionEQ, ExpansionRoK, @@ -156,8 +223,10 @@ public: // Class Methods bool IsValidRaceClassCombo(); + static bool IsValidRaceClassCombo(uint16 r, uint8 c); bool IsValidName(); - static bool IsBotNameAvailable(char *botName, std::string* errorMessage); + static bool IsValidName(std::string& name); + static bool IsBotNameAvailable(const char *botName, std::string* errorMessage); bool DeleteBot(std::string* errorMessage); void Spawn(Client* botCharacterOwner, std::string* errorMessage); virtual void SetLevel(uint8 in_level, bool command = false); @@ -230,18 +299,7 @@ public: bool HasOrMayGetAggro(); void SetDefaultBotStance(); void CalcChanceToCast(); - void CreateHealRotation( Mob* target, uint32 timer = 10000 ); - bool AddHealRotationMember( Bot* healer ); - bool RemoveHealRotationMember( Bot* healer ); - bool AddHealRotationTarget( Mob* target ); - //bool AddHealRotationTarget( const char *targetName, int index); - bool AddHealRotationTarget( Mob* target, int index); - bool RemoveHealRotationTarget( Mob* target ); - bool RemoveHealRotationTarget( int index); - void NotifyNextHealRotationMember( bool notifyNow = false ); - void ClearHealRotationLeader() { _healRotationLeader = 0; } - void ClearHealRotationMembers(); - void ClearHealRotationTargets(); + inline virtual int32 GetMaxStat(); inline virtual int32 GetMaxResist(); inline virtual int32 GetMaxSTR(); @@ -299,6 +357,9 @@ public: virtual bool AI_PursueCastCheck(); virtual bool AI_IdleCastCheck(); bool AIHealRotation(Mob* tar, bool useFastHeals); + bool GetPauseAI() { return _pauseAI; } + void SetPauseAI(bool pause_flag) { _pauseAI = pause_flag; } + // Mob AI Virtual Override Methods virtual void AI_Process(); @@ -322,15 +383,6 @@ public: virtual bool DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, CastAction_type &CastAction); virtual bool DoCastSpell(uint16 spell_id, uint16 target_id, uint16 slot = USE_ITEM_SPELL_SLOT, int32 casttime = -1, int32 mana_cost = -1, uint32* oSpellWillFinish = 0, uint32 item_slot = 0xFFFFFFFF, uint32 aa_id = 0); - // Bot Action Command Methods - bool MesmerizeTarget(Mob* target); - bool Bot_Command_Resist(int resisttype, int level); - bool Bot_Command_DireTarget(int diretype, Mob *target); - bool Bot_Command_CharmTarget(int charmtype, Mob *target); - bool Bot_Command_CalmTarget(Mob *target); - bool Bot_Command_RezzTarget(Mob *target); - bool Bot_Command_Cure(int curetype, int level); - // Bot Equipment & Inventory Class Methods void BotTradeSwapItem(Client* client, int16 lootSlot, const ItemInst* inst, const ItemInst* inst_swap, uint32 equipableSlots, std::string* errorMessage, bool swap = true); void BotTradeAddItem(uint32 id, const ItemInst* inst, int16 charges, uint32 equipableSlots, uint16 lootSlot, std::string* errorMessage, bool addToDb = true); @@ -339,20 +391,11 @@ public: uint32 GetEquipmentColor(uint8 material_slot) const; virtual void UpdateEquipmentLight() { m_Light.Type.Equipment = m_inv.FindBrightestLightType(); m_Light.Level.Equipment = m_Light.TypeToLevel(m_Light.Type.Equipment); } - // Static Class Methods - static void SaveBotGroup(Group* botGroup, std::string botGroupName, std::string* errorMessage); - static void DeleteBotGroup(std::string botGroupName, std::string* errorMessage); - static std::list LoadBotGroup(std::string botGroupName, std::string* errorMessage); - static uint32 CanLoadBotGroup(uint32 botOwnerCharacterId, std::string botGroupName, std::string* errorMessage); - static uint32 GetBotGroupIdByBotGroupName(std::string botGroupName, std::string* errorMessage); - static uint32 GetBotGroupLeaderIdByBotGroupName(std::string botGroupName); - static std::list GetBotGroupListByBotOwnerCharacterId(uint32 botOwnerCharacterId, std::string* errorMessage); - static bool DoesBotGroupNameExist(std::string botGroupName); + // Static Class Methods //static void DestroyBotRaidObjects(Client* client); // Can be removed after bot raids are dumped static uint32 GetBotIDByBotName(std::string botName); static Bot* LoadBot(uint32 botID, std::string* errorMessage); static std::list GetBotList(uint32 botOwnerCharacterID, std::string* errorMessage); - static void ProcessBotCommands(Client *c, const Seperator *sep); static std::list ListSpawnedBots(uint32 characterID, std::string* errorMessage); static uint32 SpawnedBotCount(uint32 botOwnerCharacterID, std::string* errorMessage); static uint32 CreatedBotCount(uint32 botOwnerCharacterID, std::string* errorMessage); @@ -363,15 +406,10 @@ public: static std::string ClassIdToString(uint16 classId); static std::string RaceIdToString(uint16 raceId); static bool IsBotAttackAllowed(Mob* attacker, Mob* target, bool& hasRuleDefined); - static void BotGroupOrderFollow(Group* group, Client* client); - static void BotGroupOrderGuard(Group* group, Client* client); - static void BotGroupOrderAttack(Group* group, Mob* target, Client* client); - static void BotGroupSummon(Group* group, Client* client); static Bot* GetBotByBotClientOwnerAndBotName(Client* c, std::string botName); static void ProcessBotGroupInvite(Client* c, std::string botName); static void ProcessBotGroupDisband(Client* c, std::string botName); static void BotOrderCampAll(Client* c); - static void BotHealRotationsClear( Client* c ); static void ProcessBotInspectionRequest(Bot* inspectedBot, Client* client); static std::list GetGroupedBotsByGroupId(uint32 groupId, std::string* errorMessage); static void LoadAndSpawnAllZonedBots(Client* botOwner); @@ -411,13 +449,10 @@ public: static BotSpell GetBestBotSpellForCure(Bot* botCaster, Mob* target); static BotSpell GetBestBotSpellForResistDebuff(Bot* botCaster, Mob* target); static NPCType CreateDefaultNPCTypeStructForBot(std::string botName, std::string botLastName, uint8 botLevel, uint16 botRace, uint8 botClass, uint8 gender); - static std::list GetBotsInHealRotation( Bot* leader ); // Static Bot Group Methods static bool AddBotToGroup(Bot* bot, Group* group); static bool RemoveBotFromGroup(Bot* bot, Group* group); - static bool BotGroupCreate(std::string botGroupLeaderName); - static bool BotGroupCreate(Bot* botGroupLeader); static bool GroupHasClass(Group* group, uint8 classId); static bool GroupHasClericClass(Group* group) { return GroupHasClass(group, CLERIC); } static bool GroupHasDruidClass(Group* group) { return GroupHasClass(group, DRUID); } @@ -445,26 +480,38 @@ public: uint8 GetChanceToCastBySpellType(uint16 spellType); bool IsGroupPrimaryHealer(); bool IsGroupPrimarySlower(); - bool IsBotCaster() { return (GetClass() == CLERIC || GetClass() == DRUID || GetClass() == SHAMAN || GetClass() == NECROMANCER || GetClass() == WIZARD || GetClass() == MAGICIAN || GetClass() == ENCHANTER); } - bool IsBotINTCaster() { return (GetClass() == NECROMANCER || GetClass() == WIZARD || GetClass() == MAGICIAN || GetClass() == ENCHANTER); } - bool IsBotWISCaster() { return (GetClass() == CLERIC || GetClass() == DRUID || GetClass() == SHAMAN); } + bool IsBotCaster() { return IsCasterClass(GetClass()); } + bool IsBotINTCaster() { return IsINTCasterClass(GetClass()); } + bool IsBotWISCaster() { return IsWISCasterClass(GetClass()); } bool CanHeal(); int GetRawACNoShield(int &shield_ac); bool GetHasBeenSummoned() { return _hasBeenSummoned; } const glm::vec3 GetPreSummonLocation() const { return m_PreSummonLocation; } - bool GetInHealRotation() { return _isInHealRotation; } - bool GetHealRotationActive() { return (GetInHealRotation() && _isHealRotationActive); } - bool GetHealRotationUseFastHeals() { return _healRotationUseFastHeals; } - bool GetHasHealedThisCycle() { return _hasHealedThisCycle; } - Mob* GetHealRotationTarget(); - Mob* GetHealRotationTarget(uint8 index); - Bot* GetHealRotationLeader(); - Bot* GetNextHealRotationMember(); - Bot* GetPrevHealRotationMember(); - uint8 GetNumHealRotationMembers () { return _numHealRotationMembers; } - uint32 GetHealRotationNextHealTime() { return _healRotationNextHeal; } - uint32 GetHealRotationTimer () { return _healRotationTimer; } - bool GetBardUseOutOfCombatSongs() { return _bardUseOutOfCombatSongs;} + + // new heal rotation code + bool CreateHealRotation(uint32 cycle_duration_ms = 5000, bool fast_heals = false, bool adaptive_targeting = false, bool casting_override = false); + bool DestroyHealRotation(); + bool JoinHealRotationMemberPool(std::shared_ptr* heal_rotation); + bool LeaveHealRotationMemberPool(); + + bool IsHealRotationMember() { return (m_member_of_heal_rotation.use_count() && m_member_of_heal_rotation.get()); } + bool UseHealRotationFastHeals(); + bool UseHealRotationAdaptiveTargeting(); + + bool IsHealRotationActive(); + bool IsHealRotationReady(); + bool IsHealRotationCaster(); + bool HealRotationPokeTarget(); + Mob* HealRotationTarget(); + bool AdvanceHealRotation(bool use_interval = true); + + bool IsMyHealRotationSet(); + bool AmICastingForHealRotation(); + void SetMyCastingForHealRotation(bool flag = true); + + std::shared_ptr* MemberOfHealRotation() { return &m_member_of_heal_rotation; } + + bool GetAltOutOfCombatBehavior() { return _altoutofcombatbehavior;} bool GetShowHelm() { return _showhelm; } inline virtual int32 GetAC() const { return AC; } inline virtual int32 GetSTR() const { return STR; } @@ -532,34 +579,41 @@ public: // void SetBotOwnerCharacterID(uint32 botOwnerCharacterID) { _botOwnerCharacterID = botOwnerCharacterID; } void SetRangerAutoWeaponSelect(bool enable) { GetClass() == RANGER ? _rangerAutoWeaponSelect = enable : _rangerAutoWeaponSelect = false; } void SetBotRole(BotRoleType botRole) { _botRole = botRole; } - void SetBotStance(BotStanceType botStance) { _botStance = botStance; } + void SetBotStance(BotStanceType botStance) { _botStance = ((botStance != BotStanceUnknown) ? (botStance) : (BotStancePassive)); } void SetSpellRecastTimer(int timer_index, int32 recast_delay); void SetDisciplineRecastTimer(int timer_index, int32 recast_delay); void SetHasBeenSummoned(bool s); void SetPreSummonLocation(const glm::vec3& location) { m_PreSummonLocation = location; } - void SetInHealRotation( bool inRotation ) { _isInHealRotation = inRotation; } - void SetHealRotationActive( bool isActive ) { _isHealRotationActive = isActive; } - void SetHealRotationUseFastHeals( bool useFastHeals ) { _healRotationUseFastHeals = useFastHeals; } - void SetHasHealedThisCycle( bool hasHealed ) { _hasHealedThisCycle = hasHealed; } - void SetHealRotationLeader( Bot* leader ); - void SetNextHealRotationMember( Bot* healer ); - void SetPrevHealRotationMember( Bot* healer ); - void SetHealRotationNextHealTime( uint32 nextHealTime ) { _healRotationNextHeal = nextHealTime; } - void SetHealRotationTimer( uint32 timer ) { _healRotationTimer = timer; } - void SetNumHealRotationMembers( uint8 numMembers ) { _numHealRotationMembers = numMembers; } - void SetBardUseOutOfCombatSongs(bool useOutOfCombatSongs) { _bardUseOutOfCombatSongs = useOutOfCombatSongs;} + + void SetAltOutOfCombatBehavior(bool behavior_flag) { _altoutofcombatbehavior = behavior_flag;} void SetShowHelm(bool showhelm) { _showhelm = showhelm; } + void SetBeardColor(uint8 value) { beardcolor = value; } + void SetBeard(uint8 value) { beard = value; } + void SetEyeColor1(uint8 value) { eyecolor1 = value; } + void SetEyeColor2(uint8 value) { eyecolor2 = value; } + void SetLuclinFace(uint8 value) { luclinface = value; } + void SetHairColor(uint8 value) { haircolor = value; } + void SetHairStyle(uint8 value) { hairstyle = value; } + void SetDrakkinDetails(uint32 value) { drakkin_details = value; } + void SetDrakkinHeritage(uint32 value) { drakkin_heritage = value; } + void SetDrakkinTattoo(uint32 value) { drakkin_tattoo = value; } + bool DyeArmor(int16 slot_id, uint32 rgb, bool all_flag = false, bool save_flag = true); std::string CreateSayLink(Client* botOwner, const char* message, const char* name); // Class Destructors virtual ~Bot(); + // Publicized protected/private functions + virtual void BotRangedAttack(Mob* other); // protected + uint32 GetBotItemsCount(std::string* errorMessage); // private + void BotRemoveEquipItem(int slot); // private + void RemoveBotItemBySlot(uint32 slotID, std::string* errorMessage); // private + protected: virtual void PetAIProcess(); static NPCType FillNPCTypeStruct(uint32 botSpellsID, std::string botName, std::string botLastName, uint8 botLevel, uint16 botRace, uint8 botClass, uint8 gender, float size, uint32 face, uint32 hairStyle, uint32 hairColor, uint32 eyeColor, uint32 eyeColor2, uint32 beardColor, uint32 beard, uint32 drakkinHeritage, uint32 drakkinTattoo, uint32 drakkinDetails, int32 hp, int32 mana, int32 mr, int32 cr, int32 dr, int32 fr, int32 pr, int32 corrup, int32 ac, uint32 str, uint32 sta, uint32 dex, uint32 agi, uint32 _int, uint32 wis, uint32 cha, uint32 attack); virtual void BotMeditate(bool isSitting); - virtual void BotRangedAttack(Mob* other); virtual bool CheckBotDoubleAttack(bool Triple = false); virtual int32 GetBotFocusEffect(BotfocusType bottype, uint16 spell_id); virtual int32 CalcBotFocusEffect(BotfocusType bottype, uint16 focus_id, uint16 spell_id, bool best_focus=false); @@ -607,22 +661,14 @@ private: bool _hasBeenSummoned; glm::vec3 m_PreSummonLocation; uint8 _spellCastingChances[MaxStances][MaxSpellTypes]; - bool _isInHealRotation; - bool _isHealRotationActive; - bool _healRotationUseFastHeals; - bool _hasHealedThisCycle; - uint32 _healRotationTimer; - uint32 _healRotationNextHeal; - //char _healRotationTargets[MaxHealRotationTargets][64]; - uint16 _healRotationTargets[MaxHealRotationTargets]; - uint32 _healRotationLeader; - uint32 _healRotationMemberNext; - uint32 _healRotationMemberPrev; - uint8 _numHealRotationMembers; + + std::shared_ptr m_member_of_heal_rotation; + std::map botAAs; InspectMessage_Struct _botInspectMessage; - bool _bardUseOutOfCombatSongs; + bool _altoutofcombatbehavior; bool _showhelm; + bool _pauseAI; // Private "base stats" Members int32 _baseMR; @@ -656,12 +702,9 @@ private: // Private "Inventory" Methods void GetBotItems(std::string* errorMessage, Inventory &inv); - void BotRemoveEquipItem(int slot); void BotAddEquipItem(int slot, uint32 id); uint32 GetBotItemBySlot(uint32 slotID); - void RemoveBotItemBySlot(uint32 slotID, std::string* errorMessage); void SetBotItemInSlot(uint32 slotID, uint32 itemID, const ItemInst* inst, std::string* errorMessage); - uint32 GetBotItemsCount(std::string* errorMessage); uint32 GetTotalPlayTime(); void SaveBuffs(); // Saves existing buffs to the database to persist zoning and camping void LoadBuffs(); // Retrieves saved buffs from the database on spawning diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp new file mode 100644 index 000000000..0c09c6f31 --- /dev/null +++ b/zone/bot_command.cpp @@ -0,0 +1,7312 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/* + + To add a new bot command 3 things must be done: + + 1. At the bottom of bot_command.h you must add a prototype for it. + 2. Add the function in this file. + 3. In the bot_command_init function you must add a call to bot_command_add + for your function. + + Notes: If you want an alias for your bot command, add an entry to the + `bot_command_settings` table in your database. The access level you + set with bot_command_add is the default setting if the bot command isn't + listed in the `bot_command_settings` db table. + +*/ + +#include +#include +#include +#include +#include +#include + +#ifdef _WINDOWS +#define strcasecmp _stricmp +#endif + +#include "../common/global_define.h" +#include "../common/eq_packet.h" +#include "../common/features.h" +#include "../common/guilds.h" +#include "../common/patches/patches.h" +#include "../common/ptimer.h" +#include "../common/rulesys.h" +#include "../common/serverinfo.h" +#include "../common/string_util.h" +#include "../common/eqemu_logsys.h" + + +#include "bot_command.h" +#include "bot_database.h" +#include "guild_mgr.h" +#include "map.h" +#include "doors.h" +#include "pathing.h" +#include "qglobals.h" +#include "queryserv.h" +#include "quest_parser_collection.h" +#include "string_ids.h" +#include "titles.h" +#include "water_map.h" +#include "worldserver.h" + +extern QueryServ* QServ; +extern WorldServer worldserver; +extern TaskManager *taskmanager; +void CatchSignal(int sig_num); + + +/* + * file-scope helper objects + */ +namespace +{ +//#define BCSTSPELLDUMP // only needed if you're adding/tailoring bot command spells and need a file dump + +#define m_message CC_WhiteSmoke +#define m_action CC_Yellow +#define m_note CC_Gray +#define m_usage CC_Cyan +#define m_fail CC_Red +#define m_unknown CC_Magenta + +#define HP_RATIO_DELTA 5.0f + + enum { EffectIDFirst = 1, EffectIDLast = 12 }; + +#define CLASSIDTOINDEX(x) ((x >= WARRIOR && x <= BERSERKER) ? (x - 1) : (0)) +#define EFFECTIDTOINDEX(x) ((x >= EffectIDFirst && x <= EffectIDLast) ? (x - 1) : (0)) +#define AILMENTIDTOINDEX(x) ((x >= BCEnum::AT_Blindness && x <= BCEnum::AT_Corruption) ? (x - 1) : (0)) +#define RESISTANCEIDTOINDEX(x) ((x >= BCEnum::RT_Fire && x <= BCEnum::RT_Corruption) ? (x - 1) : (0)) + + // ActionableTarget action_type +#define FRIENDLY true +#define ENEMY false +} + +bcst_map bot_command_spells; +bcst_required_bot_classes_map required_bots_map; +bcst_required_bot_classes_map_by_class required_bots_map_by_class; + + +class BCSpells +{ +public: + // + static void Load() { + bot_command_spells.clear(); + bcst_levels_map bot_levels_map; + + for (int i = BCEnum::SpellTypeFirst; i <= BCEnum::SpellTypeLast; ++i) { + bot_command_spells[static_cast(i)]; + bot_levels_map[static_cast(i)]; + } + + for (int spell_id = 2; spell_id < SPDAT_RECORDS; ++spell_id) { + if (spells[spell_id].player_1[0] == '\0') + continue; + if (spells[spell_id].targettype != ST_Target && spells[spell_id].CastRestriction != 0) // watch + continue; + + auto target_type = BCEnum::TT_None; + switch (spells[spell_id].targettype) { + case ST_GroupTeleport: + target_type = BCEnum::TT_GroupV1; + break; + case ST_AECaster: + // Disabled until bot code works correctly + //target_type = BCEnum::TT_AECaster; + break; + case ST_AEBard: + // Disabled until bot code works correctly + //target_type = BCEnum::TT_AEBard; + break; + case ST_Target: + switch (spells[spell_id].CastRestriction) { + case 0: + target_type = BCEnum::TT_Single; + break; + case 104: + target_type = BCEnum::TT_Animal; + break; + case 105: + target_type = BCEnum::TT_Plant; + break; + case 118: + target_type = BCEnum::TT_Summoned; + break; + case 120: + target_type = BCEnum::TT_Undead; + break; + default: + break; + } + break; + case ST_Self: + target_type = BCEnum::TT_Self; + break; + case ST_AETarget: + // Disabled until bot code works correctly + //target_type = BCEnum::TT_AETarget; + break; + case ST_Animal: + target_type = BCEnum::TT_Animal; + break; + case ST_Undead: + target_type = BCEnum::TT_Undead; + break; + case ST_Summoned: + target_type = BCEnum::TT_Summoned; + break; + case ST_Corpse: + target_type = BCEnum::TT_Corpse; + break; + case ST_Plant: + target_type = BCEnum::TT_Plant; + break; + case ST_Group: + target_type = BCEnum::TT_GroupV2; + break; + default: + break; + } + if (target_type == BCEnum::TT_None) + continue; + + uint8 class_levels[16] = { 0 }; + bool player_spell = false; + for (int class_type = WARRIOR; class_type <= BERSERKER; ++class_type) { + int class_index = CLASSIDTOINDEX(class_type); + if (spells[spell_id].classes[class_index] == 0 || spells[spell_id].classes[class_index] > HARD_LEVEL_CAP) + continue; + + class_levels[class_index] = spells[spell_id].classes[class_index]; + player_spell = true; + } + if (!player_spell) + continue; + + STBaseEntry* entry_prototype = nullptr; + while (true) { + switch (spells[spell_id].effectid[EFFECTIDTOINDEX(1)]) { + case SE_BindAffinity: + entry_prototype = new STBaseEntry(BCEnum::SpT_BindAffinity); + break; + case SE_Charm: + if (spells[spell_id].SpellAffectIndex != 12) + break; + entry_prototype = new STCharmEntry(); + if (spells[spell_id].ResistDiff <= -1000) + entry_prototype->SafeCastToCharm()->dire = true; + break; + case SE_Teleport: + entry_prototype = new STDepartEntry; + entry_prototype->SafeCastToDepart()->single = !BCSpells::IsGroupType(target_type); + break; + case SE_Succor: + if (!strcmp(spells[spell_id].teleport_zone, "same")) { + entry_prototype = new STEscapeEntry; + } + else { + entry_prototype = new STDepartEntry; + entry_prototype->SafeCastToDepart()->single = !BCSpells::IsGroupType(target_type); + } + break; + case SE_Translocate: + if (spells[spell_id].teleport_zone[0] == '\0') { + entry_prototype = new STSendHomeEntry(); + entry_prototype->SafeCastToSendHome()->group = BCSpells::IsGroupType(target_type); + } + else { + entry_prototype = new STDepartEntry; + entry_prototype->SafeCastToDepart()->single = !BCSpells::IsGroupType(target_type); + } + break; + case SE_ModelSize: + if (spells[spell_id].base[EFFECTIDTOINDEX(1)] > 100) { + entry_prototype = new STSizeEntry; + entry_prototype->SafeCastToSize()->size_type = BCEnum::SzT_Enlarge; + } + else if (spells[spell_id].base[EFFECTIDTOINDEX(1)] > 0 && spells[spell_id].base[EFFECTIDTOINDEX(1)] < 100) { + entry_prototype = new STSizeEntry; + entry_prototype->SafeCastToSize()->size_type = BCEnum::SzT_Reduce; + } + break; + case SE_Identify: + entry_prototype = new STBaseEntry(BCEnum::SpT_Identify); + break; + case SE_Invisibility: + if (spells[spell_id].SpellAffectIndex != 9) + break; + entry_prototype = new STInvisibilityEntry; + entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Living; + break; + case SE_SeeInvis: + if (spells[spell_id].SpellAffectIndex != 5) + break; + entry_prototype = new STInvisibilityEntry; + entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_See; + break; + case SE_InvisVsUndead: + if (spells[spell_id].SpellAffectIndex != 9) + break; + entry_prototype = new STInvisibilityEntry; + entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Undead; + break; + case SE_InvisVsAnimals: + if (spells[spell_id].SpellAffectIndex != 9) + break; + entry_prototype = new STInvisibilityEntry; + entry_prototype->SafeCastToInvisibility()->invis_type = BCEnum::IT_Animal; + break; + case SE_Mez: + if (spells[spell_id].SpellAffectIndex != 12) + break; + entry_prototype = new STBaseEntry(BCEnum::SpT_Mesmerize); + break; + case SE_Revive: + if (spells[spell_id].SpellAffectIndex != 1) + break; + entry_prototype = new STResurrectEntry(); + entry_prototype->SafeCastToResurrect()->aoe = BCSpells::IsCasterCentered(target_type); + break; + case SE_Rune: + if (spells[spell_id].SpellAffectIndex != 2) + break; + entry_prototype = new STBaseEntry(BCEnum::SpT_Rune); + break; + case SE_SummonCorpse: + entry_prototype = new STBaseEntry(BCEnum::SpT_SummonCorpse); + break; + case SE_WaterBreathing: + entry_prototype = new STBaseEntry(BCEnum::SpT_WaterBreathing); + break; + default: + break; + } + if (entry_prototype) + break; + + switch (spells[spell_id].effectid[EFFECTIDTOINDEX(2)]) { + case SE_Succor: + entry_prototype = new STEscapeEntry; + std::string is_lesser = spells[spell_id].name; + if (is_lesser.find("Lesser") != std::string::npos) + entry_prototype->SafeCastToEscape()->lesser = true; + break; + } + if (entry_prototype) + break; + + switch (spells[spell_id].effectid[EFFECTIDTOINDEX(3)]) { + case SE_Lull: + entry_prototype = new STBaseEntry(BCEnum::SpT_Lull); + break; + case SE_Levitate: // needs more criteria + entry_prototype = new STBaseEntry(BCEnum::SpT_Levitation); + break; + default: + break; + } + if (entry_prototype) + break; + + while (spells[spell_id].typedescnum == 27) { + if (!spells[spell_id].goodEffect) + break; + if (spells[spell_id].skill != SkillOffense && spells[spell_id].skill != SkillDefense) + break; + + entry_prototype = new STStanceEntry(); + if (spells[spell_id].skill == SkillOffense) + entry_prototype->SafeCastToStance()->stance_type = BCEnum::StT_Aggressive; + else + entry_prototype->SafeCastToStance()->stance_type = BCEnum::StT_Defensive; + + break; + } + if (entry_prototype) + break; + + switch (spells[spell_id].SpellAffectIndex) { + case 1: { + bool valid_spell = false; + entry_prototype = new STCureEntry; + + for (int i = EffectIDFirst; i <= EffectIDLast; ++i) { + int effect_index = EFFECTIDTOINDEX(i); + if (spells[spell_id].effectid[effect_index] != SE_Blind && spells[spell_id].base[effect_index] >= 0) + continue; + else if (spells[spell_id].effectid[effect_index] == SE_Blind && !spells[spell_id].goodEffect) + continue; + + switch (spells[spell_id].effectid[effect_index]) { + case SE_Blind: + entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Blindness)] += spells[spell_id].base[effect_index]; + break; + case SE_DiseaseCounter: + entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Disease)] += spells[spell_id].base[effect_index]; + break; + case SE_PoisonCounter: + entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Poison)] += spells[spell_id].base[effect_index]; + break; + case SE_CurseCounter: + entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Curse)] += spells[spell_id].base[effect_index]; + break; + case SE_CorruptionCounter: + entry_prototype->SafeCastToCure()->cure_value[AILMENTIDTOINDEX(BCEnum::AT_Corruption)] += spells[spell_id].base[effect_index]; + break; + default: + continue; + } + entry_prototype->SafeCastToCure()->cure_total += spells[spell_id].base[effect_index]; + valid_spell = true; + } + if (!valid_spell) { + safe_delete(entry_prototype); + entry_prototype = nullptr; + } + + break; + } + case 2: { + bool valid_spell = false; + entry_prototype = new STResistanceEntry; + + for (int i = EffectIDFirst; i <= EffectIDLast; ++i) { + int effect_index = EFFECTIDTOINDEX(i); + if (spells[spell_id].base[effect_index] <= 0) + continue; + + switch (spells[spell_id].effectid[effect_index]) { + case SE_ResistFire: + entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Fire)] += spells[spell_id].base[effect_index]; + break; + case SE_ResistCold: + entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Cold)] += spells[spell_id].base[effect_index]; + break; + case SE_ResistPoison: + entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Poison)] += spells[spell_id].base[effect_index]; + break; + case SE_ResistDisease: + entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Disease)] += spells[spell_id].base[effect_index]; + break; + case SE_ResistMagic: + entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Magic)] += spells[spell_id].base[effect_index]; + break; + case SE_ResistCorruption: + entry_prototype->SafeCastToResistance()->resist_value[RESISTANCEIDTOINDEX(BCEnum::RT_Corruption)] += spells[spell_id].base[effect_index]; + break; + default: + continue; + } + entry_prototype->SafeCastToResistance()->resist_total += spells[spell_id].base[effect_index]; + valid_spell = true; + } + if (!valid_spell) { + safe_delete(entry_prototype); + entry_prototype = nullptr; + } + + break; + } + case 7: + case 10: + if (spells[spell_id].effectdescnum != 65) + break; + entry_prototype = new STMovementSpeedEntry(); + entry_prototype->SafeCastToMovementSpeed()->group = BCSpells::IsGroupType(target_type); + break; + default: + break; + } + if (entry_prototype) + break; + + break; + } + if (!entry_prototype) + continue; + + if (target_type == BCEnum::TT_Self && (entry_prototype->BCST() != BCEnum::SpT_Stance && entry_prototype->BCST() != BCEnum::SpT_SummonCorpse)) { +#ifdef BCSTSPELLDUMP + Log.Out(Logs::General, Logs::Error, "DELETING entry_prototype (primary clause) - name: %s, target_type: %s, BCST: %s", + spells[spell_id].name, BCEnum::TargetTypeEnumToString(target_type).c_str(), BCEnum::SpellTypeEnumToString(entry_prototype->BCST()).c_str()); +#endif + safe_delete(entry_prototype); + continue; + } + if (entry_prototype->BCST() == BCEnum::SpT_Stance && target_type != BCEnum::TT_Self) { +#ifdef BCSTSPELLDUMP + Log.Out(Logs::General, Logs::Error, "DELETING entry_prototype (secondary clause) - name: %s, BCST: %s, target_type: %s", + spells[spell_id].name, BCEnum::SpellTypeEnumToString(entry_prototype->BCST()).c_str(), BCEnum::TargetTypeEnumToString(target_type).c_str()); +#endif + safe_delete(entry_prototype); + continue; + } + + assert(entry_prototype->BCST() != BCEnum::SpT_None); + + entry_prototype->spell_id = spell_id; + entry_prototype->target_type = target_type; + + bcst_levels& bot_levels = bot_levels_map[entry_prototype->BCST()]; + for (int class_type = WARRIOR; class_type <= BERSERKER; ++class_type) { + int class_index = CLASSIDTOINDEX(class_type); + if (!class_levels[class_index]) + continue; + + STBaseEntry* spell_entry = nullptr; + switch (entry_prototype->BCST()) { + case BCEnum::SpT_Charm: + if (entry_prototype->IsCharm()) + spell_entry = new STCharmEntry(entry_prototype->SafeCastToCharm()); + break; + case BCEnum::SpT_Cure: + if (entry_prototype->IsCure()) + spell_entry = new STCureEntry(entry_prototype->SafeCastToCure()); + break; + case BCEnum::SpT_Depart: + if (entry_prototype->IsDepart()) + spell_entry = new STDepartEntry(entry_prototype->SafeCastToDepart()); + break; + case BCEnum::SpT_Escape: + if (entry_prototype->IsEscape()) + spell_entry = new STEscapeEntry(entry_prototype->SafeCastToEscape()); + break; + case BCEnum::SpT_Invisibility: + if (entry_prototype->IsInvisibility()) + spell_entry = new STInvisibilityEntry(entry_prototype->SafeCastToInvisibility()); + break; + case BCEnum::SpT_MovementSpeed: + if (entry_prototype->IsMovementSpeed()) + spell_entry = new STMovementSpeedEntry(entry_prototype->SafeCastToMovementSpeed()); + break; + case BCEnum::SpT_Resistance: + if (entry_prototype->IsResistance()) + spell_entry = new STResistanceEntry(entry_prototype->SafeCastToResistance()); + break; + case BCEnum::SpT_Resurrect: + if (entry_prototype->IsResurrect()) + spell_entry = new STResurrectEntry(entry_prototype->SafeCastToResurrect()); + break; + case BCEnum::SpT_SendHome: + if (entry_prototype->IsSendHome()) + spell_entry = new STSendHomeEntry(entry_prototype->SafeCastToSendHome()); + break; + case BCEnum::SpT_Size: + if (entry_prototype->IsSize()) + spell_entry = new STSizeEntry(entry_prototype->SafeCastToSize()); + break; + case BCEnum::SpT_Stance: + if (entry_prototype->IsStance()) + spell_entry = new STStanceEntry(entry_prototype->SafeCastToStance()); + break; + default: + spell_entry = new STBaseEntry(entry_prototype); + break; + } + + assert(spell_entry); + + spell_entry->caster_class = class_type; + spell_entry->spell_level = class_levels[class_index]; + + bot_command_spells[spell_entry->BCST()].push_back(spell_entry); + + if (bot_levels.find(class_type) == bot_levels.end() || bot_levels[class_type] > class_levels[class_index]) + bot_levels[class_type] = class_levels[class_index]; + } + + delete(entry_prototype); + } + + remove_inactive(); + order_all(); + load_teleport_zone_names(); + build_strings(bot_levels_map); + status_report(); + +#ifdef BCSTSPELLDUMP + spell_dump(); +#endif + + } + + static void Unload() { + for (auto map_iter : bot_command_spells) { + if (map_iter.second.empty()) + continue; + for (auto list_iter : map_iter.second) + safe_delete(list_iter); + map_iter.second.clear(); + } + bot_command_spells.clear(); + required_bots_map.clear(); + required_bots_map_by_class.clear(); + } + + static bool IsCasterCentered(BCEnum::TType target_type) { + switch (target_type) { + case BCEnum::TT_AECaster: + case BCEnum::TT_AEBard: + return true; + default: + return false; + } + } + + static bool IsGroupType(BCEnum::TType target_type) { + switch (target_type) { + case BCEnum::TT_GroupV1: + case BCEnum::TT_GroupV2: + return true; + default: + return false; + } + } + +private: + static void remove_inactive() { + if (bot_command_spells.empty()) + return; + + for (auto map_iter = bot_command_spells.begin(); map_iter != bot_command_spells.end(); ++map_iter) { + if (map_iter->second.empty()) + continue; + + bcst_list* spells_list = &map_iter->second; + bcst_list* removed_spells_list = new bcst_list; + + spells_list->remove(nullptr); + spells_list->remove_if([removed_spells_list](STBaseEntry* l) { + if (l->spell_id < 2 || l->spell_id >= SPDAT_RECORDS || strlen(spells[l->spell_id].name) < 3) { + removed_spells_list->push_back(l); + return true; + } + else { + return false; + } + }); + + for (auto del_iter : *removed_spells_list) + safe_delete(del_iter); + removed_spells_list->clear(); + + if (RuleI(Bots, CommandSpellRank) == 1) { + spells_list->sort([](STBaseEntry* l, STBaseEntry* r) { + if (spells[l->spell_id].spellgroup < spells[r->spell_id].spellgroup) + return true; + if (spells[l->spell_id].spellgroup == spells[r->spell_id].spellgroup && l->caster_class < r->caster_class) + return true; + if (spells[l->spell_id].spellgroup == spells[r->spell_id].spellgroup && l->caster_class == r->caster_class && spells[l->spell_id].rank < spells[r->spell_id].rank) + return true; + + return false; + }); + spells_list->unique([removed_spells_list](STBaseEntry* l, STBaseEntry* r) { + std::string r_name = spells[r->spell_id].name; + if (spells[l->spell_id].spellgroup == spells[r->spell_id].spellgroup && l->caster_class == r->caster_class && spells[l->spell_id].rank < spells[r->spell_id].rank) { + removed_spells_list->push_back(r); + return true; + } + + return false; + }); + + for (auto del_iter : *removed_spells_list) + safe_delete(del_iter); + removed_spells_list->clear(); + } + + if (RuleI(Bots, CommandSpellRank) == 2) { + spells_list->remove_if([removed_spells_list](STBaseEntry* l) { + std::string l_name = spells[l->spell_id].name; + if (spells[l->spell_id].rank == 10) { + removed_spells_list->push_back(l); + return true; + } + if (l_name.find("III") == (l_name.size() - 3)) { + removed_spells_list->push_back(l); + return true; + } + if (l_name.find("III ") == (l_name.size() - 4)) { + removed_spells_list->push_back(l); + return true; + } + + return false; + }); + + for (auto del_iter : *removed_spells_list) + safe_delete(del_iter); + removed_spells_list->clear(); + } + + // needs rework + if (RuleI(Bots, CommandSpellRank) == 2 || RuleI(Bots, CommandSpellRank) == 3) { + spells_list->sort([](STBaseEntry* l, STBaseEntry* r) { + if (spells[l->spell_id].spellgroup < spells[r->spell_id].spellgroup) + return true; + if (spells[l->spell_id].spellgroup == spells[r->spell_id].spellgroup && l->caster_class < r->caster_class) + return true; + if (spells[l->spell_id].spellgroup == spells[r->spell_id].spellgroup && l->caster_class == r->caster_class && spells[l->spell_id].rank > spells[r->spell_id].rank) + return true; + + return false; + }); + spells_list->unique([removed_spells_list](STBaseEntry* l, STBaseEntry* r) { + std::string l_name = spells[l->spell_id].name; + if (spells[l->spell_id].spellgroup == spells[r->spell_id].spellgroup && l->caster_class == r->caster_class && spells[l->spell_id].rank > spells[r->spell_id].rank) { + removed_spells_list->push_back(r); + return true; + } + + return false; + }); + + for (auto del_iter : *removed_spells_list) + safe_delete(del_iter); + removed_spells_list->clear(); + } + + safe_delete(removed_spells_list); + } + } + + static void order_all() { + // Example of a macro'd lambda using anonymous property dereference: + // #define XXX(p) ([](const <_Ty>* l, const <_Ty>* r) { return (l->p < r->p); }) + + +#define LT_STBASE(l, r, p) (l->p < r->p) +#define LT_STCHARM(l, r, p) (l->SafeCastToCharm()->p < r->SafeCastToCharm()->p) +#define LT_STCURE(l, r, p) (l->SafeCastToCure()->p < r->SafeCastToCure()->p) +#define LT_STCURE_VAL_ID(l, r, p, ctid) (l->SafeCastToCure()->p[AILMENTIDTOINDEX(ctid)] < r->SafeCastToCure()->p[AILMENTIDTOINDEX(ctid)]) +#define LT_STDEPART(l, r, p) (l->SafeCastToDepart()->p < r->SafeCastToDepart()->p) +#define LT_STESCAPE(l, r, p) (l->SafeCastToEscape()->p < r->SafeCastToEscape()->p) +#define LT_STINVISIBILITY(l, r, p) (l->SafeCastToInvisibility()->p < r->SafeCastToInvisibility()->p) +#define LT_STRESISTANCE(l, r, p) (l->SafeCastToResistance()->p < r->SafeCastToResistance()->p) +#define LT_STRESISTANCE_VAL_ID(l, r, p, rtid) (l->SafeCastToResistance()->p[RESISTANCEIDTOINDEX(rtid)] < r->SafeCastToResistance()->p[RESISTANCEIDTOINDEX(rtid)) +#define LT_STSTANCE(l, r, p) (l->SafeCastToStance()->p < r->SafeCastToStance()->p) +#define LT_SPELLS(l, r, p) (spells[l->spell_id].p < spells[r->spell_id].p) +#define LT_SPELLS_EFFECT_ID(l, r, p, eid) (spells[l->spell_id].p[EFFECTIDTOINDEX(eid)] < spells[r->spell_id].p[EFFECTIDTOINDEX(eid)]) +#define LT_SPELLS_STR(l, r, s) (strcasecmp(spells[l->spell_id].s, spells[r->spell_id].s) < 0) + +#define EQ_STBASE(l, r, p) (l->p == r->p) +#define EQ_STCHARM(l, r, p) (l->SafeCastToCharm()->p == r->SafeCastToCharm()->p) +#define EQ_STCURE(l, r, p) (l->SafeCastToCure()->p == r->SafeCastToCure()->p) +#define EQ_STCURE_VAL_ID(l, r, p, ctid) (l->SafeCastToCure()->p[AILMENTIDTOINDEX(ctid)] == r->SafeCastToCure()->p[AILMENTIDTOINDEX(ctid)]) +#define EQ_STDEPART(l, r, p) (l->SafeCastToDepart()->p == r->SafeCastToDepart()->p) +#define EQ_STESCAPE(l, r, p) (l->SafeCastToEscape()->p == r->SafeCastToEscape()->p) +#define EQ_STINVISIBILITY(l, r, p) (l->SafeCastToInvisibility()->p == r->SafeCastToInvisibility()->p) +#define EQ_STRESISTANCE(l, r, p) (l->SafeCastToResistance()->p == r->SafeCastToResistance()->p) +#define EQ_STRESISTANCE_VAL_ID(l, r, p, rtid) (l->SafeCastToResistance()->p[RESISTANCEIDTOINDEX(rtid)] == r->SafeCastToResistance()->p[RESISTANCEIDTOINDEX(rtid)) +#define EQ_STSTANCE(l, r, p) (l->SafeCastToStance()->p == r->SafeCastToStance()->p) +#define EQ_SPELLS(l, r, p) (spells[l->spell_id].p == spells[r->spell_id].p) +#define EQ_SPELLS_EFFECT_ID(l, r, p, eid) (spells[l->spell_id].p[EFFECTIDTOINDEX(eid)] == spells[r->spell_id].p[EFFECTIDTOINDEX(eid)]) +#define EQ_SPELLS_STR(l, r, s) (strcasecmp(spells[l->spell_id].s, spells[r->spell_id].s) == 0) + +#define GT_STBASE(l, r, p) (l->p > r->p) +#define GT_STCHARM(l, r, p) (l->SafeCastToCharm()->p > r->SafeCastToCharm()->p) +#define GT_STCURE(l, r, p) (l->SafeCastToCure()->p > r->SafeCastToCure()->p) +#define GT_STCURE_VAL_ID(l, r, p, ctid) (l->SafeCastToCure()->p[AILMENTIDTOINDEX(ctid)] > r->SafeCastToCure()->p[AILMENTIDTOINDEX(ctid)]) +#define GT_STDEPART(l, r, p) (l->SafeCastToDepart()->p > r->SafeCastToDepart()->p) +#define GT_STESCAPE(l, r, p) (l->SafeCastToEscape()->p > r->SafeCastToEscape()->p) +#define GT_STINVISIBILITY(l, r, p) (l->SafeCastToInvisibility()->p > r->SafeCastToInvisibility()->p) +#define GT_STRESISTANCE(l, r, p) (l->SafeCastToResistance()->p > r->SafeCastToResistance()->p) +#define GT_STRESISTANCE_VAL_ID(l, r, p, rtid) (l->SafeCastToResistance()->p[RESISTANCEIDTOINDEX(rtid)] > r->SafeCastToResistance()->p[RESISTANCEIDTOINDEX(rtid)) +#define GT_STSTANCE(l, r, p) (l->SafeCastToStance()->p > r->SafeCastToStance()->p) +#define GT_SPELLS(l, r, p) (spells[l->spell_id].p > spells[r->spell_id].p) +#define GT_SPELLS_EFFECT_ID(l, r, p, eid) (spells[l->spell_id].p[EFFECTIDTOINDEX(eid)] > spells[r->spell_id].p[EFFECTIDTOINDEX(eid)]) +#define GT_SPELLS_STR(l, r, s) (strcasecmp(spells[l->spell_id].s, spells[r->spell_id].s) > 0) + + + for (auto map_iter = bot_command_spells.begin(); map_iter != bot_command_spells.end(); ++map_iter) { + if (map_iter->second.size() < 2) + continue; + + auto spell_type = map_iter->first; + bcst_list* spell_list = &map_iter->second; + switch (spell_type) { + case BCEnum::SpT_BindAffinity: + if (RuleB(Bots, PreferNoManaCommandSpells)) { + spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { + if (LT_SPELLS(l, r, mana)) + return true; + if (EQ_SPELLS(l, r, mana) && LT_STBASE(l, r, target_type)) + return true; + if (EQ_SPELLS(l, r, mana) && EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, spell_level)) + return true; + if (EQ_SPELLS(l, r, mana) && EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + return true; + + return false; + }); + } + else { + spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { + if (LT_STBASE(l, r, target_type)) + return true; + if (EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, spell_level)) + return true; + if (EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + return true; + + return false; + }); + } + continue; + case BCEnum::SpT_Charm: + spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { + if (LT_SPELLS(l, r, ResistDiff)) + return true; + if (EQ_SPELLS(l, r, ResistDiff) && LT_STBASE(l, r, target_type)) + return true; + if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max, 1)) + return true; + if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 1) && LT_STBASE(l, r, spell_level)) + return true; + if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + return true; + + return false; + }); + continue; + case BCEnum::SpT_Cure: // per-use sorting in command handler + spell_list->sort([](STBaseEntry* l, STBaseEntry* r) { + if (l->spell_id < r->spell_id) + return true; + if (l->spell_id == r->spell_id && LT_STBASE(l, r, caster_class)) + return true; + + return false; + }); + continue; + case BCEnum::SpT_Depart: + spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { + if (LT_STBASE(l, r, target_type)) + return true; + if (EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, caster_class)) + return true; + if (EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, caster_class) && LT_STBASE(l, r, spell_level)) + return true; + if (EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, caster_class) && EQ_STBASE(l, r, spell_level) && LT_SPELLS_STR(l, r, name)) + return true; + + return false; + }); + continue; + case BCEnum::SpT_Escape: + spell_list->sort([](STBaseEntry* l, STBaseEntry* r) { + if (LT_STESCAPE(l, r, lesser)) + return true; + if (EQ_STESCAPE(l, r, lesser) && LT_STBASE(l, r, target_type)) + return true; + if (EQ_STESCAPE(l, r, lesser) && EQ_STBASE(l, r, target_type) && GT_STBASE(l, r, spell_level)) + return true; + if (EQ_STESCAPE(l, r, lesser) && EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + return true; + + return false; + }); + continue; + case BCEnum::SpT_Identify: +#ifdef PREFER_NO_MANA_COST_SPELLS + spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { + if (LT_SPELLS(l, r, mana)) + return true; + if (EQ_SPELLS(l, r, mana) && LT_STBASE(l, r, target_type)) + return true; + if (EQ_SPELLS(l, r, mana) && EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, spell_level)) + return true; + if (EQ_SPELLS(l, r, mana) && EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + return true; + + return false; + }); +#else + spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { + if (LT_STBASE(l, r, target_type)) + return true; + if (EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, spell_level)) + return true; + if (EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + return true; + + return false; + }); +#endif + continue; + case BCEnum::SpT_Invisibility: + spell_list->sort([](STBaseEntry* l, STBaseEntry* r) { + if (LT_STINVISIBILITY(l, r, invis_type)) + return true; + if (EQ_STINVISIBILITY(l, r, invis_type) && LT_STBASE(l, r, target_type)) + return true; + if (EQ_STINVISIBILITY(l, r, invis_type) && EQ_STBASE(l, r, target_type) && GT_STBASE(l, r, spell_level)) + return true; + if (EQ_STINVISIBILITY(l, r, invis_type) && EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + return true; + return false; + }); + continue; + case BCEnum::SpT_Levitation: + spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { + if (LT_STBASE(l, r, target_type)) + return true; + if (EQ_STBASE(l, r, target_type) && LT_SPELLS(l, r, zonetype)) + return true; + if (EQ_STBASE(l, r, target_type) && EQ_SPELLS(l, r, zonetype) && GT_STBASE(l, r, spell_level)) + return true; + if (EQ_STBASE(l, r, target_type) && EQ_SPELLS(l, r, zonetype) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + return true; + + return false; + }); + continue; + case BCEnum::SpT_Lull: + spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { + if (LT_SPELLS(l, r, ResistDiff)) + return true; + if (EQ_SPELLS(l, r, ResistDiff) && LT_STBASE(l, r, target_type)) + return true; + if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max, 3)) + return true; + if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 3) && LT_STBASE(l, r, spell_level)) + return true; + if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 3) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + return true; + + return false; + }); + continue; + case BCEnum::SpT_Mesmerize: + spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { + if (GT_SPELLS(l, r, ResistDiff)) + return true; + if (EQ_SPELLS(l, r, ResistDiff) && LT_STBASE(l, r, target_type)) + return true; + if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max, 1)) + return true; + if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 1) && GT_STBASE(l, r, spell_level)) + return true; + if (EQ_SPELLS(l, r, ResistDiff) && EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + return true; + + return false; + }); + continue; + case BCEnum::SpT_MovementSpeed: + spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { + if (LT_STBASE(l, r, target_type)) + return true; + if (EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, base, 2)) + return true; + if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base, 2) && LT_STBASE(l, r, spell_level)) + return true; + if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, base, 2) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + return true; + + return false; + }); + continue; + case BCEnum::SpT_Resistance: // per-use sorting in command handler + spell_list->sort([](STBaseEntry* l, STBaseEntry* r) { + if (l->spell_id < r->spell_id) + return true; + if (l->spell_id == r->spell_id && LT_STBASE(l, r, caster_class)) + return true; + + return false; + }); + continue; + case BCEnum::SpT_Resurrect: + spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { + if (GT_SPELLS_EFFECT_ID(l, r, base, 1)) + return true; + if (EQ_SPELLS_EFFECT_ID(l, r, base, 1) && LT_STBASE(l, r, target_type)) + return true; + if (EQ_SPELLS_EFFECT_ID(l, r, base, 1) && EQ_STBASE(l, r, target_type) && LT_STBASE(l, r, spell_level)) + return true; + if (EQ_SPELLS_EFFECT_ID(l, r, base, 1) && EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + return true; + + return false; + }); + continue; + case BCEnum::SpT_Rune: + spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { + if (LT_STBASE(l, r, target_type)) + return true; + if (EQ_STBASE(l, r, target_type) && GT_SPELLS_EFFECT_ID(l, r, max, 1)) + return true; + if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 1) && LT_STBASE(l, r, spell_level)) + return true; + if (EQ_STBASE(l, r, target_type) && EQ_SPELLS_EFFECT_ID(l, r, max, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + return true; + + return false; + }); + continue; + case BCEnum::SpT_SendHome: + spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { + if (LT_STBASE(l, r, target_type)) + return true; + if (EQ_STBASE(l, r, target_type) && GT_STBASE(l, r, spell_level)) + return true; + if (EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + return true; + + return false; + }); + continue; + case BCEnum::SpT_Size: + spell_list->sort([](STBaseEntry* l, STBaseEntry* r) { + if (LT_STBASE(l, r, target_type)) + return true; + + auto l_size_type = l->SafeCastToSize()->size_type; + auto r_size_type = r->SafeCastToSize()->size_type; + if (l_size_type < r_size_type) + return true; + if (l_size_type == BCEnum::SzT_Enlarge && r_size_type == BCEnum::SzT_Enlarge) { + if (EQ_STBASE(l, r, target_type) && GT_SPELLS(l, r, base, 1)) + return true; + if (EQ_STBASE(l, r, target_type) && EQ_SPELLS(l, r, base, 1) && GT_STBASE(l, r, spell_level)) + return true; + if (EQ_STBASE(l, r, target_type) && EQ_SPELLS(l, r, base, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + return true; + } + if (l_size_type == BCEnum::SzT_Reduce && r_size_type == BCEnum::SzT_Reduce) { + if (EQ_STBASE(l, r, target_type) && LT_SPELLS(l, r, base, 1)) + return true; + if (EQ_STBASE(l, r, target_type) && EQ_SPELLS(l, r, base, 1) && GT_STBASE(l, r, spell_level)) + return true; + if (EQ_STBASE(l, r, target_type) && EQ_SPELLS(l, r, base, 1) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + return true; + } + + return false; + }); + continue; + case BCEnum::SpT_Stance: + spell_list->sort([](STBaseEntry* l, STBaseEntry* r) { + if (LT_STSTANCE(l, r, stance_type)) + return true; + + return false; + }); + continue; + case BCEnum::SpT_SummonCorpse: + spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { + if (GT_SPELLS_EFFECT_ID(l, r, base, 1)) + return true; + if (EQ_SPELLS_EFFECT_ID(l, r, base, 1) && LT_STBASE(l, r, spell_level)) + return true; + if (EQ_SPELLS_EFFECT_ID(l, r, base, 1) && EQ_STBASE(l, r, spell_level) && EQ_STBASE(l, r, caster_class)) + return true; + + return false; + }); + continue; + case BCEnum::SpT_WaterBreathing: + spell_list->sort([](const STBaseEntry* l, const STBaseEntry* r) { + if (LT_STBASE(l, r, target_type)) + return true; + if (EQ_STBASE(l, r, target_type) && GT_STBASE(l, r, spell_level)) + return true; + if (EQ_STBASE(l, r, target_type) && EQ_STBASE(l, r, spell_level) && LT_STBASE(l, r, caster_class)) + return true; + + return false; + }); + continue; + default: + continue; + } + } + } + + static void load_teleport_zone_names() { + auto depart_list = &bot_command_spells[BCEnum::SpT_Depart]; + if (depart_list->empty()) + return; + + std::string query = "SELECT `short_name`, `long_name` FROM `zone` WHERE '' NOT IN (`short_name`, `long_name`)"; + auto results = database.QueryDatabase(query); + if (!results.Success()) { + Log.Out(Logs::General, Logs::Error, "load_teleport_zone_names() - Error in zone names query: %s", results.ErrorMessage().c_str()); + return; + } + + std::map zone_names; + for (auto row = results.begin(); row != results.end(); ++row) + zone_names[row[0]] = row[1]; + + for (auto list_iter = depart_list->begin(); list_iter != depart_list->end();) { + auto test_iter = zone_names.find(spells[(*list_iter)->spell_id].teleport_zone); + if (test_iter == zone_names.end()) { + list_iter = depart_list->erase(list_iter); + continue; + } + + (*list_iter)->SafeCastToDepart()->long_name = test_iter->second; + ++list_iter; + } + + } + + static void build_strings(bcst_levels_map& bot_levels_map) { + for (int i = BCEnum::SpellTypeFirst; i <= BCEnum::SpellTypeLast; ++i) + helper_bots_string(static_cast(i), bot_levels_map[static_cast(i)]); + } + + static void status_report() { + Log.Out(Logs::General, Logs::Commands, "load_bot_command_spells(): - 'RuleI(Bots, CommandSpellRank)' set to %i.", RuleI(Bots, CommandSpellRank)); + if (bot_command_spells.empty()) { + Log.Out(Logs::General, Logs::Error, "load_bot_command_spells() - 'bot_command_spells' is empty."); + return; + } + + for (int i = BCEnum::SpellTypeFirst; i <= BCEnum::SpellTypeLast; ++i) + Log.Out(Logs::General, Logs::Commands, "load_bot_command_spells(): - '%s' returned %u spell entries.", + BCEnum::SpellTypeEnumToString(static_cast(i)).c_str(), bot_command_spells[static_cast(i)].size()); + } + + static void helper_bots_string(BCEnum::SpType type_index, bcst_levels& bot_levels) { + for (int i = WARRIOR; i <= BERSERKER; ++i) + required_bots_map_by_class[type_index][i] = "Unavailable..."; + + if (bot_levels.empty()) { + required_bots_map[type_index] = "This command is currently unavailable..."; + return; + } + + required_bots_map[type_index] = ""; + + auto map_size = bot_levels.size(); + while (bot_levels.size()) { + bcst_levels::iterator test_iter = bot_levels.begin(); + for (bcst_levels::iterator levels_iter = bot_levels.begin(); levels_iter != bot_levels.end(); ++levels_iter) { + if (levels_iter->second < test_iter->second) + test_iter = levels_iter; + if (strcasecmp(Bot::ClassIdToString(levels_iter->first).c_str(), Bot::ClassIdToString(test_iter->first).c_str()) < 0 && levels_iter->second <= test_iter->second) + test_iter = levels_iter; + } + + std::string bot_segment; + if (bot_levels.size() == map_size) + bot_segment = "%s(%u)"; + else if (bot_levels.size() > 1) + bot_segment = ", %s(%u)"; + else + bot_segment = " or %s(%u)"; + + required_bots_map[type_index].append(StringFormat(bot_segment.c_str(), Bot::ClassIdToString(test_iter->first).c_str(), test_iter->second)); + required_bots_map_by_class[type_index][test_iter->first] = StringFormat("%s(%u)", Bot::ClassIdToString(test_iter->first).c_str(), test_iter->second); + bot_levels.erase(test_iter); + } + } + +#ifdef BCSTSPELLDUMP + static void spell_dump() { + std::ofstream spell_dump; + spell_dump.open(StringFormat("bcs_dump/spell_dump_%i.txt", getpid()), std::ios_base::app | std::ios_base::out); + + if (bot_command_spells.empty()) { + spell_dump << "BCSpells::spell_dump() - 'bot_command_spells' map is empty.\n"; + spell_dump.close(); + return; + } + + int entry_count = 0; + for (int i = BCEnum::SpellTypeFirst; i <= BCEnum::SpellTypeLast; ++i) { + auto bcst_id = static_cast(i); + spell_dump << StringFormat("BCSpells::spell_dump(): - '%s' returned %u spells:\n", + BCEnum::SpellTypeEnumToString(bcst_id).c_str(), bot_command_spells[bcst_id].size()); + + bcst_list& map_entry = bot_command_spells[bcst_id]; + for (auto list_iter = map_entry.begin(); list_iter != map_entry.end(); ++list_iter) { + STBaseEntry* list_entry = *list_iter; + int spell_id = list_entry->spell_id; + spell_dump << StringFormat("\"%20s\" tt:%02u/cc:%02u/cl:%03u", + ((strlen(spells[spell_id].name) > 20) ? (std::string(spells[spell_id].name).substr(0, 20).c_str()) : (spells[spell_id].name)), + list_entry->target_type, + list_entry->caster_class, + list_entry->spell_level + ); + + spell_dump << StringFormat(" /mn:%05u/RD:%06i/zt:%02i/d#:%06i/td#:%05i/ed#:%05i/SAI:%03u", + spells[spell_id].mana, + spells[spell_id].ResistDiff, + spells[spell_id].zonetype, + spells[spell_id].descnum, + spells[spell_id].typedescnum, + spells[spell_id].effectdescnum, + spells[spell_id].SpellAffectIndex + ); + + for (int i = EffectIDFirst; i <= 3/*EffectIDLast*/; ++i) { + int effect_index = EFFECTIDTOINDEX(i); + spell_dump << StringFormat(" /e%02i:%04i/b%02i:%06i/m%02i:%06i", + i, spells[spell_id].effectid[effect_index], i, spells[spell_id].base[effect_index], i, spells[spell_id].max[effect_index]); + } + + switch (list_entry->BCST()) { + case BCEnum::SpT_Charm: + spell_dump << StringFormat(" /d:%c", ((list_entry->SafeCastToCharm()->dire) ? ('T') : ('F'))); + break; + case BCEnum::SpT_Cure: + spell_dump << ' '; + for (int i = 0; i < BCEnum::AilmentTypeCount; ++i) { + spell_dump << StringFormat("/cv%02i:%03i", i, list_entry->SafeCastToCure()->cure_value[i]); + } + break; + case BCEnum::SpT_Depart: { + std::string long_name = list_entry->SafeCastToDepart()->long_name.c_str(); + spell_dump << StringFormat(" /ln:%20s", ((long_name.size() > 20) ? (long_name.substr(0, 20).c_str()) : (long_name.c_str()))); + break; + } + case BCEnum::SpT_Escape: + spell_dump << StringFormat(" /l:%c", ((list_entry->SafeCastToEscape()->lesser) ? ('T') : ('F'))); + break; + case BCEnum::SpT_Invisibility: + spell_dump << StringFormat(" /it:%02i", list_entry->SafeCastToInvisibility()->invis_type); + break; + case BCEnum::SpT_MovementSpeed: + spell_dump << StringFormat(" /g:%c", ((list_entry->SafeCastToMovementSpeed()->group) ? ('T') : ('F'))); + break; + case BCEnum::SpT_Resistance: + spell_dump << ' '; + for (int i = 0; i < BCEnum::ResistanceTypeCount; ++i) { + spell_dump << StringFormat("/rv%02i:%03i", i, list_entry->SafeCastToResistance()->resist_value[i]); + } + break; + case BCEnum::SpT_Resurrect: + spell_dump << StringFormat(" /aoe:%c", ((list_entry->SafeCastToResurrect()->aoe) ? ('T') : ('F'))); + break; + case BCEnum::SpT_SendHome: + spell_dump << StringFormat(" /g:%c", ((list_entry->SafeCastToSendHome()->group) ? ('T') : ('F'))); + break; + case BCEnum::SpT_Size: + spell_dump << StringFormat(" /st:%02i", list_entry->SafeCastToSize()->size_type); + break; + case BCEnum::SpT_Stance: + spell_dump << StringFormat(" /st:%02i", list_entry->SafeCastToStance()->stance_type); + break; + default: + break; + } + + spell_dump << "\n"; + ++entry_count; + } + + spell_dump << StringFormat("required_bots_map[%s] = \"%s\"\n", + BCEnum::SpellTypeEnumToString(static_cast(i)).c_str(), required_bots_map[static_cast(i)].c_str()); + + spell_dump << "\n"; + } + + spell_dump << StringFormat("Total bcs entry count: %i\n", entry_count); + spell_dump.close(); + } +#endif +}; + + +//struct bcl_struct *bot_command_list; // the actual linked list of bot commands +int bot_command_count; // how many bot commands we have + +// this is the pointer to the dispatch function, updated once +// init has been performed to point at the real function +int (*bot_command_dispatch)(Client *,char const *) = bot_command_not_avail; + +// TODO: Find out what these are for... +void bot_command_bestz(Client *c, const Seperator *message); +void bot_command_pf(Client *c, const Seperator *message); + +std::map bot_command_list; +std::map bot_command_aliases; + +// All allocated BotCommandRecords get put in here so they get deleted on shutdown +LinkedList cleanup_bot_command_list; + + +/* + * bot_command_not_avail + * This is the default dispatch function when commands aren't loaded. + * + * Parameters: + * not used + * + */ +int bot_command_not_avail(Client *c, const char *message) +{ + c->Message(m_fail, "Bot commands not available."); + return -1; +} + + +/************************************************************************** +/* the rest below here could be in a dynamically loaded module eventually * +/*************************************************************************/ + +/* + +Access Levels: + +0 Normal +10 * Steward * +20 * Apprentice Guide * +50 * Guide * +80 * QuestTroupe * +81 * Senior Guide * +85 * GM-Tester * +90 * EQ Support * +95 * GM-Staff * +100 * GM-Admin * +150 * GM-Lead Admin * +160 * QuestMaster * +170 * GM-Areas * +180 * GM-Coder * +200 * GM-Mgmt * +250 * GM-Impossible * + +*/ + +/* + * bot_command_init + * initializes the bot command list, call at startup + * + * Parameters: + * none + * + * When adding a new bot command, only hard-code 'real' bot commands - + * all command aliases are added later through a database call + * + */ +int bot_command_init(void) +{ + bot_command_aliases.clear(); + + if ( + bot_command_add("actionable", "Lists actionable command arguments and use descriptions", 0, bot_command_actionable) || + bot_command_add("aggressive", "Orders a bot to use a aggressive discipline", 0, bot_command_aggressive) || + bot_command_add("attack", "Orders bots to attack a designated target", 0, bot_command_attack) || + bot_command_add("bindaffinity", "Orders a bot to attempt an affinity binding", 0, bot_command_bind_affinity) || + bot_command_add("bot", "Lists the available bot management [subcommands]", 0, bot_command_bot) || + bot_command_add("botappearance", "Lists the available bot appearance [subcommands]", 0, bot_subcommand_bot_appearance) || + bot_command_add("botbeardcolor", "Changes the beard color of a bot", 0, bot_subcommand_bot_beard_color) || + bot_command_add("botbeardstyle", "Changes the beard style of a bot", 0, bot_subcommand_bot_beard_style) || + bot_command_add("botcamp", "Orders a bot(s) to camp", 0, bot_subcommand_bot_camp) || + bot_command_add("botclone", "Creates a copy of a bot", 200, bot_subcommand_bot_clone) || + bot_command_add("botcreate", "Creates a new bot", 0, bot_subcommand_bot_create) || + bot_command_add("botdelete", "Deletes all record of a bot", 0, bot_subcommand_bot_delete) || + bot_command_add("botdetails", "Changes the Drakkin details of a bot", 0, bot_subcommand_bot_details) || + bot_command_add("botdyearmor", "Changes the color of a bot's (bots') armor", 0, bot_subcommand_bot_dye_armor) || + bot_command_add("boteyes", "Changes the eye colors of a bot", 0, bot_subcommand_bot_eyes) || + bot_command_add("botface", "Changes the facial appearance of your bot", 0, bot_subcommand_bot_face) || + bot_command_add("botfollowdistance", "Changes the follow distance(s) of a bot(s)", 0, bot_subcommand_bot_follow_distance) || + bot_command_add("botgroup", "Lists the available bot-group [subcommands]", 0, bot_command_botgroup) || + bot_command_add("botgroupaddmember", "Adds a member to a bot-group", 0, bot_subcommand_botgroup_add_member) || + bot_command_add("botgroupcreate", "Creates a bot-group and designates a leader", 0, bot_subcommand_botgroup_create) || + bot_command_add("botgroupdelete", "Deletes a bot-group and releases its members", 0, bot_subcommand_botgroup_delete) || + bot_command_add("botgrouplist", "Lists all of your existing bot-groups", 0, bot_subcommand_botgroup_list) || + bot_command_add("botgroupload", "Loads all members of a bot-group", 0, bot_subcommand_botgroup_load) || + bot_command_add("botgroupremovemember", "Removes a bot from its bot-group", 0, bot_subcommand_botgroup_remove_member) || + bot_command_add("bothaircolor", "Changes the hair color of a bot", 0, bot_subcommand_bot_hair_color) || + bot_command_add("bothairstyle", "Changes the hairstyle of a bot", 0, bot_subcommand_bot_hairstyle) || + bot_command_add("botheritage", "Changes the Drakkin heritage of a bot", 0, bot_subcommand_bot_heritage) || + bot_command_add("botinspectmessage", "Changes the inspect message of a bot", 0, bot_subcommand_bot_inspect_message) || + bot_command_add("botlist", "Lists the bots that you own", 0, bot_subcommand_bot_list) || + bot_command_add("botoutofcombat", "Toggles your bot between standard and out-of-combat spell/skill use - if any specialized behaviors exist", 0, bot_subcommand_bot_out_of_combat) || + bot_command_add("botreport", "Orders a bot to report its readiness", 0, bot_subcommand_bot_report) || + bot_command_add("botspawn", "Spawns a created bot", 0, bot_subcommand_bot_spawn) || + bot_command_add("botstance", "Changes the stance of a bot", 0, bot_subcommand_bot_stance) || + bot_command_add("botsummon", "Summons bot(s) to your location", 0, bot_subcommand_bot_summon) || + bot_command_add("bottattoo", "Changes the Drakkin tattoo of a bot", 0, bot_subcommand_bot_tattoo) || + bot_command_add("bottogglearcher", "Toggles a archer bot between melee and ranged weapon use", 0, bot_subcommand_bot_toggle_archer) || + bot_command_add("bottogglehelm", "Toggles the helm visibility of a bot between shown and hidden", 0, bot_subcommand_bot_toggle_helm) || + bot_command_add("botupdate", "Updates a bot to reflect any level changes that you have experienced", 0, bot_subcommand_bot_update) || + bot_command_add("botwoad", "Changes the Barbarian woad of a bot", 0, bot_subcommand_bot_woad) || + bot_command_add("charm", "Attempts to have a bot charm your target", 0, bot_command_charm) || + bot_command_add("circle", "Orders a Druid bot to open a magical doorway to a specified destination", 0, bot_subcommand_circle) || + bot_command_add("cure", "Orders a bot to remove any ailments", 0, bot_command_cure) || + bot_command_add("defensive", "Orders a bot to use a defensive discipline", 0, bot_command_defensive) || + bot_command_add("depart", "Orders a bot to open a magical doorway to a specified destination", 0, bot_command_depart) || + bot_command_add("escape", "Orders a bot to send a target group to a safe location within the zone", 0, bot_command_escape) || + bot_command_add("findaliases", "Find available aliases for a bot command", 0, bot_command_find_aliases) || + bot_command_add("follow", "Orders bots to follow a designated target", 0, bot_command_follow) || + bot_command_add("guard", "Orders bots to guard their current positions", 0, bot_command_guard) || + bot_command_add("healrotation", "Lists the available bot heal rotation [subcommands]", 0, bot_command_heal_rotation) || + bot_command_add("healrotationadaptivetargeting", "Enables or disables adaptive targeting within the heal rotation instance", 0, bot_subcommand_heal_rotation_adaptive_targeting) || + bot_command_add("healrotationaddmember", "Adds a bot to a heal rotation instance", 0, bot_subcommand_heal_rotation_add_member) || + bot_command_add("healrotationaddtarget", "Adds target to a heal rotation instance", 0, bot_subcommand_heal_rotation_add_target) || + bot_command_add("healrotationadjustcritical", "Adjusts the critial HP limit of the heal rotation instance's Class Armor Type criteria", 0, bot_subcommand_heal_rotation_adjust_critical) || + bot_command_add("healrotationadjustsafe", "Adjusts the safe HP limit of the heal rotation instance's Class Armor Type criteria", 0, bot_subcommand_heal_rotation_adjust_safe) || + bot_command_add("healrotationcastingoverride", "Enables or disables casting overrides within the heal rotation instance", 0, bot_subcommand_heal_rotation_casting_override) || + bot_command_add("healrotationchangeinterval", "Changes casting interval between members within the heal rotation instance", 0, bot_subcommand_heal_rotation_change_interval) || + bot_command_add("healrotationcleartargets", "Removes all targets from a heal rotation instance", 0, bot_subcommand_heal_rotation_clear_targets) || + bot_command_add("healrotationcreate", "Creates a bot heal rotation instance and designates a leader", 0, bot_subcommand_heal_rotation_create) || + bot_command_add("healrotationfastheals", "Enables or disables fast heals within the heal rotation instance", 0, bot_subcommand_heal_rotation_fast_heals) || + bot_command_add("healrotationlist", "Reports heal rotation instance(s) information", 0, bot_subcommand_heal_rotation_list) || + bot_command_add("healrotationremovemember", "Removes a bot from a heal rotation instance", 0, bot_subcommand_heal_rotation_remove_member) || + bot_command_add("healrotationremovetarget", "Removes target from a heal rotations instance", 0, bot_subcommand_heal_rotation_remove_target) || + bot_command_add("healrotationresetlimits", "Resets all Class Armor Type HP limit criteria in a heal rotation to its default value", 0, bot_subcommand_heal_rotation_reset_limits) || + bot_command_add("healrotationstart", "Starts a heal rotation", 0, bot_subcommand_heal_rotation_start) || + bot_command_add("healrotationstop", "Stops a heal rotation", 0, bot_subcommand_heal_rotation_stop) || + bot_command_add("help", "List available commands and their description - specify partial command as argument to search", 0, bot_command_help) || + bot_command_add("hold", "Suspends a bot's AI processing until released", 0, bot_command_hold) || + bot_command_add("identify", "Orders a bot to cast an item identification spell", 0, bot_command_identify) || + bot_command_add("inventory", "Lists the available bot inventory [subcommands]", 0, bot_command_inventory) || + bot_command_add("inventorygive", "Gives the item on your cursor to a bot", 0, bot_subcommand_inventory_give) || + bot_command_add("inventorylist", "Lists all items in a bot's inventory", 0, bot_subcommand_inventory_list) || + bot_command_add("inventoryremove", "Removes an item from a bot's inventory", 0, bot_subcommand_inventory_remove) || + bot_command_add("invisibility", "Orders a bot to cast a cloak of invisibility, or allow them to be seen", 0, bot_command_invisibility) || + bot_command_add("levitation", "Orders a bot to cast a levitation spell", 0, bot_command_levitation) || + bot_command_add("lull", "Orders a bot to cast a pacification spell", 0, bot_command_lull) || + bot_command_add("mesmerize", "Orders a bot to cast a mesmerization spell", 0, bot_command_mesmerize) || + bot_command_add("movementspeed", "Orders a bot to cast a movement speed enhancement spell", 0, bot_command_movement_speed) || + bot_command_add("pet", "Lists the available bot pet [subcommands]", 0, bot_command_pet) || + bot_command_add("petremove", "Orders a bot to remove its pet", 0, bot_subcommand_pet_remove) || + bot_command_add("petsettype", "Orders a Magician bot to use a specified pet type", 0, bot_subcommand_pet_set_type) || + bot_command_add("picklock", "Orders a capable bot to pick the lock of the closest door", 0, bot_command_pick_lock) || + bot_command_add("portal", "Orders a Wizard bot to open a magical doorway to a specified destination", 0, bot_subcommand_portal) || + bot_command_add("pull", "Orders a designated bot to 'pull' an enemy", 0, bot_command_pull) || + bot_command_add("release", "Releases a suspended bot's AI processing (with hate list wipe)", 0, bot_command_release) || + bot_command_add("resistance", "Orders a bot to cast a specified resistance buff", 0, bot_command_resistance) || + bot_command_add("resurrect", "Orders a bot to resurrect a player's (players') corpse(s)", 0, bot_command_resurrect) || + bot_command_add("rune", "Orders a bot to cast a rune of protection", 0, bot_command_rune) || + bot_command_add("sendhome", "Orders a bot to open a magical doorway home", 0, bot_command_send_home) || + bot_command_add("size", "Orders a bot to change a player's size", 0, bot_command_size) || + bot_command_add("summoncorpse", "Orders a bot to summon a corpse to its feet", 0, bot_command_summon_corpse) || + bot_command_add("taunt", "Toggles taunt use by a bot", 0, bot_command_taunt) || + bot_command_add("track", "Orders a capable bot to track enemies", 0, bot_command_track) || + bot_command_add("waterbreathing", "Orders a bot to cast a water breathing spell", 0, bot_command_water_breathing) + ) { + bot_command_deinit(); + return -1; + } + + std::map>> bot_command_settings; + botdb.GetCommandSettings(bot_command_settings); + + auto working_bcl = bot_command_list; + for (auto working_bcl_iter : working_bcl) { + auto bot_command_settings_iter = bot_command_settings.find(working_bcl_iter.first); + if (bot_command_settings_iter == bot_command_settings.end()) { + if (working_bcl_iter.second->access == 0) + Log.Out(Logs::General, Logs::Commands, "bot_command_init(): Warning: Bot Command '%s' defaulting to access level 0!", working_bcl_iter.first.c_str()); + continue; + } + + working_bcl_iter.second->access = bot_command_settings_iter->second.first; + Log.Out(Logs::General, Logs::Commands, "bot_command_init(): - Bot Command '%s' set to access level %d.", working_bcl_iter.first.c_str(), bot_command_settings_iter->second.first); + if (bot_command_settings_iter->second.second.empty()) + continue; + + for (auto alias_iter : bot_command_settings_iter->second.second) { + if (alias_iter.empty()) + continue; + if (bot_command_list.find(alias_iter) != bot_command_list.end()) { + Log.Out(Logs::General, Logs::Commands, "bot_command_init(): Warning: Alias '%s' already exists as a bot command - skipping!", alias_iter.c_str()); + continue; + } + + bot_command_list[alias_iter] = working_bcl_iter.second; + bot_command_aliases[alias_iter] = working_bcl_iter.first; + + Log.Out(Logs::General, Logs::Commands, "bot_command_init(): - Alias '%s' added to bot command '%s'.", alias_iter.c_str(), bot_command_aliases[alias_iter].c_str()); + } + } + + bot_command_dispatch = bot_command_real_dispatch; + + BCSpells::Load(); + + return bot_command_count; +} + + +/* + * bot_command_deinit + * clears the bot command list, freeing resources + * + * Parameters: + * none + * + */ +void bot_command_deinit(void) +{ + bot_command_list.clear(); + bot_command_aliases.clear(); + + bot_command_dispatch = bot_command_not_avail; + bot_command_count = 0; + + BCSpells::Unload(); +} + + +/* + * bot_command_add + * adds a bot command to the bot command list; used by bot_command_init + * + * Parameters: + * bot_command_string - the command ex: "spawn" + * desc - text description of bot command for #help + * access - default access level required to use command + * function - pointer to function that handles command + * + */ +int bot_command_add(std::string bot_command_name, const char *desc, int access, BotCmdFuncPtr function) +{ + if (bot_command_name.empty()) { + Log.Out(Logs::General, Logs::Error, "bot_command_add() - Bot command added with empty name string - check bot_command.cpp."); + return -1; + } + if (function == nullptr) { + Log.Out(Logs::General, Logs::Error, "bot_command_add() - Bot command '%s' added without a valid function pointer - check bot_command.cpp.", bot_command_name.c_str()); + return -1; + } + if (bot_command_list.count(bot_command_name) != 0) { + Log.Out(Logs::General, Logs::Error, "bot_command_add() - Bot command '%s' is a duplicate bot command name - check bot_command.cpp.", bot_command_name.c_str()); + return -1; + } + for (auto iter : bot_command_list) { + if (iter.second->function != function) + continue; + Log.Out(Logs::General, Logs::Error, "bot_command_add() - Bot command '%s' equates to an alias of '%s' - check bot_command.cpp.", bot_command_name.c_str(), iter.first.c_str()); + return -1; + } + + BotCommandRecord *bcr = new BotCommandRecord; + bcr->access = access; + bcr->desc = desc; + bcr->function = function; + + bot_command_list[bot_command_name] = bcr; + bot_command_aliases[bot_command_name] = bot_command_name; + cleanup_bot_command_list.Append(bcr); + bot_command_count++; + + return 0; +} + + +/* + * + * bot_command_real_dispatch + * Calls the correct function to process the client's command string. + * Called from Client::ChannelMessageReceived if message starts with + * bot command character (^). + * + * Parameters: + * c - pointer to the calling client object + * message - what the client typed + * + */ +int bot_command_real_dispatch(Client *c, const char *message) +{ + Seperator sep(message, ' ', 10, 100, true); // "three word argument" should be considered 1 arg + + bot_command_log_command(c, message); + + std::string cstr(sep.arg[0]+1); + + if(bot_command_list.count(cstr) != 1) { + return(-2); + } + + BotCommandRecord *cur = bot_command_list[cstr]; + if(c->Admin() < cur->access){ + c->Message(m_fail, "Your access level is not high enough to use this bot command."); + return(-1); + } + + /* QS: Player_Log_Issued_Commands */ + if (RuleB(QueryServ, PlayerLogIssuedCommandes)){ + std::string event_desc = StringFormat("Issued bot command :: '%s' in zoneid:%i instid:%i", message, c->GetZoneID(), c->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Issued_Commands, c->CharacterID(), event_desc); + } + + if(cur->access >= COMMANDS_LOGGING_MIN_STATUS) { + Log.Out(Logs::General, Logs::Commands, "%s (%s) used bot command: %s (target=%s)", c->GetName(), c->AccountName(), message, c->GetTarget()?c->GetTarget()->GetName():"NONE"); + } + + if(cur->function == nullptr) { + Log.Out(Logs::General, Logs::Error, "Bot command '%s' has a null function\n", cstr.c_str()); + return(-1); + } else { + //dispatch C++ bot command + cur->function(c, &sep); // dispatch bot command + } + return 0; + +} + +void bot_command_log_command(Client *c, const char *message) +{ + int admin = c->Admin(); + + bool continueevents = false; + switch (zone->loglevelvar) { //catch failsafe + case 9: // log only LeadGM + if ((admin >= 150) && (admin <200)) + continueevents = true; + break; + case 8: // log only GM + if ((admin >= 100) && (admin <150)) + continueevents = true; + break; + case 1: + if ((admin >= 200)) + continueevents = true; + break; + case 2: + if ((admin >= 150)) + continueevents = true; + break; + case 3: + if ((admin >= 100)) + continueevents = true; + break; + case 4: + if ((admin >= 80)) + continueevents = true; + break; + case 5: + if ((admin >= 20)) + continueevents = true; + break; + case 6: + if ((admin >= 10)) + continueevents = true; + break; + case 7: + continueevents = true; + break; + default: + break; + } + + if (continueevents) + database.logevents(c->AccountName(), c->AccountID(), admin,c->GetName(), c->GetTarget()?c->GetTarget()->GetName():"None", "BotCommand", message, 1); +} + + +/* + * helper functions by use + */ +namespace MyBots +{ + static bool IsMyBot(Client *bot_owner, Mob *my_bot) { + if (!bot_owner || !my_bot || !my_bot->IsBot()) + return false; + + auto test_bot = my_bot->CastToBot(); + if (!test_bot->GetOwner() || !test_bot->GetOwner()->IsClient() || test_bot->GetOwner()->CastToClient() != bot_owner) + return false; + + return true; + } + + static bool IsMyBotInTargetsGroup(Client *bot_owner, Mob *grouped_bot) { + if (!bot_owner || !grouped_bot || !grouped_bot->GetGroup() || !IsMyBot(bot_owner, grouped_bot)) + return false; + + auto target_mob = bot_owner->GetTarget(); + if (!target_mob) + return false; + + if (!target_mob->GetGroup() || (!target_mob->IsClient() && !target_mob->IsBot())) + return false; + + return (grouped_bot->GetGroup() == target_mob->GetGroup()); + } + + static bool IsMyBotInPlayerGroup(Client *bot_owner, Mob *grouped_bot, Client *grouped_player) { + if (!bot_owner || !grouped_player || !grouped_player->GetGroup() || !grouped_bot || !grouped_bot->GetGroup() || !IsMyBot(bot_owner, grouped_bot)) + return false; + + return (grouped_player->GetGroup() == grouped_bot->GetGroup()); + } + + static void UniquifySBL(std::list &sbl) { + sbl.remove(nullptr); + sbl.sort(); + sbl.unique(); + } + + static void PopulateSBL_ByTargetedBot(Client *bot_owner, std::list &sbl, bool clear_list = true) { + if (clear_list) + sbl.clear(); + + if (IsMyBot(bot_owner, bot_owner->GetTarget())) + sbl.push_back(bot_owner->GetTarget()->CastToBot()); + + if (!clear_list) + UniquifySBL(sbl); + } + + static void PopulateSBL_ByNamedBot(Client *bot_owner, std::list &sbl, const char* name, bool clear_list = true) { + if (clear_list) + sbl.clear(); + if (!bot_owner || !name) + return; + + auto selectable_bot_list = entity_list.GetBotsByBotOwnerCharacterID(bot_owner->CharacterID()); + for (auto bot_iter : selectable_bot_list) { + if (!strcasecmp(bot_iter->GetCleanName(), name)) { + sbl.push_back(bot_iter); + return; + } + } + + if (!clear_list) + UniquifySBL(sbl); + } + + static void PopulateSBL_ByMyGroupedBots(Client *bot_owner, std::list &sbl, bool clear_list = true) { + if (clear_list) + sbl.clear(); + if (!bot_owner) + return; + + if (!bot_owner->GetGroup()) + return; + + std::list group_list; + bot_owner->GetGroup()->GetBotList(group_list); + for (auto member_iter : group_list) { + if (IsMyBot(bot_owner, member_iter)) + sbl.push_back(member_iter); + } + + if (!clear_list) + UniquifySBL(sbl); + } + + static void PopulateSBL_ByBotGroup(Client *bot_owner, std::list &sbl, const char* name, bool clear_list = true) { + if (clear_list) + sbl.clear(); + if (!bot_owner) + return; + + std::list selectable_bot_list; + if (name) + PopulateSBL_ByNamedBot(bot_owner, selectable_bot_list, name); + else if (IsMyBot(bot_owner, bot_owner->GetTarget())) + selectable_bot_list.push_back(bot_owner->GetTarget()->CastToBot()); + + if (selectable_bot_list.empty()) + return; + if (!selectable_bot_list.front()->GetGroup() || !IsMyBot(bot_owner, selectable_bot_list.front()->GetGroup()->GetLeader())) + return; + + std::list group_list; + selectable_bot_list.front()->GetGroup()->GetMemberList(group_list); + for (auto member_iter : group_list) { + if (IsMyBot(bot_owner, member_iter)) + sbl.push_back(member_iter->CastToBot()); + } + + if (!clear_list) + UniquifySBL(sbl); + } + + static void PopulateSBL_ByTargetsGroupedBots(Client *bot_owner, std::list &sbl, bool clear_list = true) { + if (clear_list) + sbl.clear(); + if (!bot_owner) + return; + + auto target_mob = bot_owner->GetTarget(); + if (!target_mob || !target_mob->GetGroup() || (!target_mob->IsClient() && !target_mob->IsBot())) + return; + + std::list group_list; + target_mob->GetGroup()->GetBotList(group_list); + for (auto member_iter : group_list) { + if (IsMyBot(bot_owner, member_iter)) + sbl.push_back(member_iter); + } + + if (!clear_list) + UniquifySBL(sbl); + } + + static void PopulateSBL_ByNamesGroupedBots(Client *bot_owner, std::list &sbl, const char* name, bool clear_list = true) { + if (clear_list) + sbl.clear(); + if (!bot_owner || !name) + return; + + Mob* named_mob = nullptr; + std::list l_mob_list; + entity_list.GetMobList(l_mob_list); + for (auto mob_iter : l_mob_list) { + if (!strcasecmp(mob_iter->GetCleanName(), name)) { + named_mob = mob_iter; + break; + } + } + if (!named_mob || !named_mob->GetGroup() || (!named_mob->IsClient() && !named_mob->IsBot())) + return; + + std::list group_list; + named_mob->GetGroup()->GetBotList(group_list); + for (auto member_iter : group_list) { + if (IsMyBot(bot_owner, member_iter)) + sbl.push_back(member_iter); + } + + if (!clear_list) + UniquifySBL(sbl); + } + + static void PopulateSBL_ByHealRotation(Client *bot_owner, std::list &sbl, const char* name, bool clear_list = true) { + if (clear_list) + sbl.clear(); + if (!bot_owner || (!name && !bot_owner->GetTarget())) + return; + + std::list selectable_bot_list; + if (name) + PopulateSBL_ByNamedBot(bot_owner, selectable_bot_list, name); + else + PopulateSBL_ByTargetedBot(bot_owner, selectable_bot_list); + + if (selectable_bot_list.empty() || !selectable_bot_list.front()->IsHealRotationMember()) + return; + + auto hrm = (*selectable_bot_list.front()->MemberOfHealRotation())->MemberList(); + for (auto hrm_iter : *hrm) { + if (IsMyBot(bot_owner, hrm_iter)) + sbl.push_back(hrm_iter); + } + + auto hrt = (*selectable_bot_list.front()->MemberOfHealRotation())->TargetList(); + for (auto hrt_iter : *hrt) { + if (IsMyBot(bot_owner, hrt_iter)) + sbl.push_back(hrt_iter->CastToBot()); + } + + UniquifySBL(sbl); + } + + static void PopulateSBL_ByHealRotationMembers(Client *bot_owner, std::list &sbl, const char* name, bool clear_list = true) { + if (clear_list) + sbl.clear(); + if (!bot_owner || (!name && !bot_owner->GetTarget())) + return; + + std::list selectable_bot_list; + if (name) + PopulateSBL_ByNamedBot(bot_owner, selectable_bot_list, name); + else + PopulateSBL_ByTargetedBot(bot_owner, selectable_bot_list); + + if (selectable_bot_list.empty() || !selectable_bot_list.front()->IsHealRotationMember()) + return; + + auto hrm = (*selectable_bot_list.front()->MemberOfHealRotation())->MemberList(); + for (auto hrm_iter : *hrm) { + if (IsMyBot(bot_owner, hrm_iter)) + sbl.push_back(hrm_iter); + } + + if (!clear_list) + UniquifySBL(sbl); + } + + static void PopulateSBL_ByHealRotationTargets(Client *bot_owner, std::list &sbl, const char* name, bool clear_list = true) { + if (clear_list) + sbl.clear(); + if (!bot_owner || (!name && !bot_owner->GetTarget())) + return; + + std::list selectable_bot_list; + if (name) + PopulateSBL_ByNamedBot(bot_owner, selectable_bot_list, name); + else + PopulateSBL_ByTargetedBot(bot_owner, selectable_bot_list); + + if (selectable_bot_list.empty() || !selectable_bot_list.front()->IsHealRotationMember()) + return; + + auto hrm = (*selectable_bot_list.front()->MemberOfHealRotation())->TargetList(); + for (auto hrm_iter : *hrm) { + if (IsMyBot(bot_owner, hrm_iter)) + sbl.push_back(static_cast(hrm_iter)); + } + + if (!clear_list) + UniquifySBL(sbl); + } + + static void PopulateSBL_BySpawnedBots(Client *bot_owner, std::list &sbl) { // should be used for most spell casting commands + sbl.clear(); + if (!bot_owner) + return; + + sbl = entity_list.GetBotsByBotOwnerCharacterID(bot_owner->CharacterID()); + sbl.remove(nullptr); + } +}; + +namespace ActionableTarget +{ + static bool AmIInPlayerGroup(Client *bot_owner, Client *grouped_player) { + if (!bot_owner || !grouped_player || !bot_owner->GetGroup() || !grouped_player->GetGroup()) + return false; + + return (bot_owner->GetGroup() == grouped_player->GetGroup()); + } + + static Client* AsSingle_ByPlayer(Client *bot_owner, bool return_me_on_null_target = true) { + if (!bot_owner) + return nullptr; + + if (!bot_owner->GetTarget()) { + if (return_me_on_null_target) + return bot_owner; + else + return nullptr; + } + + if (!bot_owner->GetTarget()->IsClient()) + return nullptr; + + return bot_owner->GetTarget()->CastToClient(); + } + + static Client* AsGroupMember_ByPlayer(Client *bot_owner, bool return_me_on_null_target = true) { + if (!bot_owner) + return nullptr; + + if (!bot_owner->GetTarget()) { + if (return_me_on_null_target) + return bot_owner; + else + return nullptr; + } + + if (!bot_owner->GetTarget()->IsClient() || !AmIInPlayerGroup(bot_owner, bot_owner->GetTarget()->CastToClient())) + return nullptr; + + return bot_owner->GetTarget()->CastToClient(); + } + + static Corpse* AsCorpse_ByPlayer(Client *bot_owner) { + if (!bot_owner || !bot_owner->GetTarget() || !bot_owner->GetTarget()->IsPlayerCorpse()) + return nullptr; + + return bot_owner->GetTarget()->CastToCorpse(); + } + + static Mob* AsSingle_ByAttackable(Client *bot_owner) { + if (!bot_owner || !bot_owner->IsAttackAllowed(bot_owner->GetTarget())) + return nullptr; + + return bot_owner->GetTarget(); + } + + static bool IsFriendlyAllowed(Mob* target_mob) { + if (!target_mob || target_mob->IsClient() || target_mob->IsBot() || (target_mob->IsPet() && target_mob->GetOwner() && (target_mob->GetOwner()->IsClient() || target_mob->GetOwner()->IsBot())) || target_mob->IsPlayerCorpse()) + return true; + + return false; + } + + static Mob* VerifyFriendly(Client* bot_owner, BCEnum::TType target_type, bool return_me_on_null_target = true) { + if (!bot_owner || target_type == BCEnum::TT_None) + return nullptr; + + auto target_mob = bot_owner->GetTarget(); + if (!IsFriendlyAllowed(target_mob)) + return nullptr; + + Mob* verified_friendly = nullptr; + switch (target_type) { + case BCEnum::TT_Single: + case BCEnum::TT_GroupV1: + case BCEnum::TT_GroupV2: + case BCEnum::TT_AETarget: + verified_friendly = target_mob; + break; + case BCEnum::TT_Animal: + if (target_mob && target_mob->GetBodyType() == BT_Animal) + verified_friendly = target_mob; + break; + case BCEnum::TT_Undead: + if (target_mob && target_mob->GetBodyType() == BT_Undead) + verified_friendly = target_mob; + break; + case BCEnum::TT_Summoned: + if (target_mob && target_mob->GetBodyType() == BT_Summoned) + verified_friendly = target_mob; + break; + case BCEnum::TT_Plant: + if (target_mob && target_mob->GetBodyType() == BT_Plant) + verified_friendly = target_mob; + break; + case BCEnum::TT_Corpse: + if (target_mob && target_mob->IsCorpse()) + verified_friendly = target_mob; + break; + default: + return nullptr; + } + + if (return_me_on_null_target && !target_mob && !verified_friendly) { + switch (target_type) { + case BCEnum::TT_Single: + case BCEnum::TT_GroupV1: + case BCEnum::TT_GroupV2: + case BCEnum::TT_AETarget: + verified_friendly = bot_owner; + break; + default: + break; + } + } + + return verified_friendly; + } + + static Mob* VerifyEnemy(Client* bot_owner, BCEnum::TType target_type) { + if (!bot_owner || target_type == BCEnum::TT_None) + return nullptr; + + auto target_mob = bot_owner->GetTarget(); + if (!target_mob || !bot_owner->IsAttackAllowed(target_mob)) + return nullptr; + + Mob* verified_enemy = nullptr; + switch (target_type) { + case BCEnum::TT_Animal: + if (target_mob->GetBodyType() == BT_Animal) + verified_enemy = target_mob; + break; + case BCEnum::TT_Undead: + if (target_mob->GetBodyType() == BT_Undead) + verified_enemy = target_mob; + break; + case BCEnum::TT_Summoned: + if (target_mob->GetBodyType() == BT_Summoned) + verified_enemy = target_mob; + break; + case BCEnum::TT_Plant: + if (target_mob->GetBodyType() == BT_Plant) + verified_enemy = target_mob; + break; + case BCEnum::TT_Single: + case BCEnum::TT_GroupV1: + case BCEnum::TT_GroupV2: + case BCEnum::TT_AETarget: + verified_enemy = target_mob; + break; + case BCEnum::TT_Corpse: + if (target_mob->IsCorpse()) + verified_enemy = target_mob; + break; + default: + return nullptr; + } + + return verified_enemy; + } + + class Types { + Mob* target[BCEnum::TargetTypeCount]; + bool target_set[BCEnum::TargetTypeCount]; + + public: + Types() { Clear(); } + + void Clear() { + for (int i = BCEnum::TT_None; i <= BCEnum::TargetTypeLast; ++i) { + target[i] = nullptr; + target_set[i] = false; + } + target_set[BCEnum::TT_None] = true; + } + + Mob* Select(Client* bot_owner, BCEnum::TType target_type, bool action_type, bool return_me_on_null_target = true) { + if (target_set[target_type]) + return target[target_type]; + + if (action_type == FRIENDLY) + target[target_type] = VerifyFriendly(bot_owner, target_type, return_me_on_null_target); + else + target[target_type] = VerifyEnemy(bot_owner, target_type); + target_set[target_type] = true; + + return target[target_type]; + } + }; +}; + +namespace ActionableBots +{ + enum ABType { + ABT_None = 0, + ABT_Target, + ABT_ByName, + ABT_OwnerGroup, + ABT_BotGroup, + ABT_TargetGroup, + ABT_NamesGroup, + ABT_HealRotation, + ABT_HealRotationMembers, + ABT_HealRotationTargets, + ABT_Spawned, + ABT_All + }; + + enum ABMask { + ABM_None = 0, + ABM_Target = (1 << (ABT_Target - 1)), + ABM_ByName = (1 << (ABT_ByName - 1)), + ABM_OwnerGroup = (1 << (ABT_OwnerGroup - 1)), + ABM_BotGroup = (1 << (ABT_BotGroup - 1)), + ABM_TargetGroup = (1 << (ABT_TargetGroup - 1)), + ABM_NamesGroup = (1 << (ABT_NamesGroup - 1)), + ABM_HealRotation = (1 << (ABT_HealRotation - 1)), + ABM_HealRotationMembers = (1 << (ABT_HealRotationMembers - 1)), + ABM_HealRotationTargets = (1 << (ABT_HealRotationTargets - 1)), + ABM_Spawned = (1 << (ABT_Spawned - 1)), + ABM_All = (1 << (ABT_All - 1)), + ABM_Spawned_All = (3 << (ABT_Spawned - 1)), + ABM_NoFilter = ~0, + // grouped values + ABM_Type1 = (ABM_Target | ABM_ByName | ABM_OwnerGroup | ABM_BotGroup | ABM_TargetGroup | ABM_NamesGroup | ABM_HealRotationTargets | ABM_Spawned), + ABM_Type2 = (ABM_ByName | ABM_OwnerGroup | ABM_BotGroup | ABM_NamesGroup | ABM_HealRotation | ABM_Spawned) + }; + + // Populates 'sbl' + static ABType PopulateSBL(Client* bot_owner, std::string ab_type_arg, std::list &sbl, int ab_mask, const char* name = nullptr, bool clear_list = true, bool suppress_message = false) { + if (clear_list) + sbl.clear(); + if (!bot_owner) + return ABT_None; + + auto ab_type = ABT_None; + if (!ab_type_arg.compare("target") || ab_type_arg.empty()) + ab_type = ABT_Target; + else if (!ab_type_arg.compare("byname")) + ab_type = ABT_ByName; + else if (!ab_type_arg.compare("ownergroup")) + ab_type = ABT_OwnerGroup; + else if (!ab_type_arg.compare("botgroup")) + ab_type = ABT_BotGroup; + else if (!ab_type_arg.compare("targetgroup")) + ab_type = ABT_TargetGroup; + else if (!ab_type_arg.compare("namesgroup")) + ab_type = ABT_NamesGroup; + else if (!ab_type_arg.compare("healrotation")) + ab_type = ABT_HealRotation; + else if (!ab_type_arg.compare("healrotationmembers")) + ab_type = ABT_HealRotationMembers; + else if (!ab_type_arg.compare("healrotationtargets")) + ab_type = ABT_HealRotationTargets; + else if (!ab_type_arg.compare("spawned")) + ab_type = ABT_Spawned; + else if (!ab_type_arg.compare("all")) + ab_type = ABT_All; + + if (ab_type_arg.empty()) + ab_type_arg = "target"; + + switch (ab_type) { + case ABT_Target: + if (ab_mask & ABM_Target) + MyBots::PopulateSBL_ByTargetedBot(bot_owner, sbl, clear_list); + break; + case ABT_ByName: + if (ab_mask & ABM_ByName) + MyBots::PopulateSBL_ByNamedBot(bot_owner, sbl, name, clear_list); + break; + case ABT_OwnerGroup: + if (ab_mask & ABM_OwnerGroup) + MyBots::PopulateSBL_ByMyGroupedBots(bot_owner, sbl, clear_list); + break; + case ABT_BotGroup: + if (ab_mask & ABM_BotGroup) + MyBots::PopulateSBL_ByBotGroup(bot_owner, sbl, name, clear_list); + break; + case ABT_TargetGroup: + if (ab_mask & ABM_TargetGroup) + MyBots::PopulateSBL_ByTargetsGroupedBots(bot_owner, sbl, clear_list); + break; + case ABT_NamesGroup: + if (ab_mask & ABM_NamesGroup) + MyBots::PopulateSBL_ByNamesGroupedBots(bot_owner, sbl, name, clear_list); + break; + case ABT_HealRotation: + if (ab_mask & ABM_HealRotation) + MyBots::PopulateSBL_ByHealRotation(bot_owner, sbl, name, clear_list); + break; + case ABT_HealRotationMembers: + if (ab_mask & ABM_HealRotationMembers) + MyBots::PopulateSBL_ByHealRotationMembers(bot_owner, sbl, name, clear_list); + break; + case ABT_HealRotationTargets: + if (ab_mask & ABM_HealRotationTargets) + MyBots::PopulateSBL_ByHealRotationTargets(bot_owner, sbl, name, clear_list); + break; + case ABT_Spawned: + case ABT_All: + if (ab_mask & ABM_Spawned_All) + MyBots::PopulateSBL_BySpawnedBots(bot_owner, sbl); + break; + default: + break; + } + if (sbl.empty() && ab_type != ABT_All) { + if (suppress_message) + return ABT_None; + + if (!ab_mask) + bot_owner->Message(m_fail, "Command passed null 'ActionableBot' criteria"); + else if (ab_mask & ab_type) + bot_owner->Message(m_fail, "You have no spawned bots meeting this criteria - type: '%s', name: '%s'", ab_type_arg.c_str(), ((name) ? (name) : (""))); + else + bot_owner->Message(m_fail, "This command does not allow 'ActionableBot' criteria '%s'", ab_type_arg.c_str()); + return ABT_None; + } + + return ab_type; + } + + // Returns single, scoped bot + static Bot* AsGroupMember_ByClass(Client *bot_owner, Client *bot_grouped_player, uint8 cls, bool petless = false) { + if (!bot_owner || !bot_grouped_player) + return nullptr; + if (!bot_grouped_player->GetGroup()) + return nullptr; + + std::list group_list; + bot_grouped_player->GetGroup()->GetMemberList(group_list); + for (auto member_iter : group_list) { + if (!MyBots::IsMyBot(bot_owner, member_iter)) + continue; + if (member_iter->GetClass() != cls) + continue; + if (petless && member_iter->GetPet()) + continue; + + return static_cast(member_iter); + } + + return nullptr; + } + + static Bot* AsGroupMember_ByMinLevelAndClass(Client *bot_owner, Client *bot_grouped_player, uint8 minlvl, uint8 cls, bool petless = false) { + // This function can be nixed if we can enforce bot level as owner level..and the level check can then be moved to the spell loop in the command function + if (!bot_owner || !bot_grouped_player) + return nullptr; + if (!bot_grouped_player->GetGroup()) + return nullptr; + + std::list group_list; + bot_grouped_player->GetGroup()->GetMemberList(group_list); + for (auto member_iter : group_list) { + if (!MyBots::IsMyBot(bot_owner, member_iter)) + continue; + if (member_iter->GetLevel() < minlvl || member_iter->GetClass() != cls) + continue; + if (petless && member_iter->GetPet()) + continue; + + return static_cast(member_iter); + } + + return nullptr; + } + + static Bot* AsSpawned_ByClass(Client *bot_owner, std::list &sbl, uint8 cls, bool petless = false) { + if (!bot_owner) + return nullptr; + + for (auto bot_iter : sbl) { + if (!MyBots::IsMyBot(bot_owner, bot_iter)) + continue; + if (bot_iter->GetClass() != cls) + continue; + if (petless && bot_iter->GetPet()) + continue; + + return bot_iter; + } + + return nullptr; + } + + static Bot* AsSpawned_ByMinLevelAndClass(Client *bot_owner, std::list &sbl, uint8 minlvl, uint8 cls, bool petless = false) { + // This function can be nixed if we can enforce bot level as owner level..and the level check can then be moved to the spell loop in the command function + if (!bot_owner) + return nullptr; + + for (auto bot_iter : sbl) { + if (!MyBots::IsMyBot(bot_owner, bot_iter)) + continue; + if (bot_iter->GetLevel() < minlvl || bot_iter->GetClass() != cls) + continue; + if (petless && bot_iter->GetPet()) + continue; + + return bot_iter; + } + + return nullptr; + } + + static Bot* AsTarget_ByBot(Client *bot_owner) { + if (!bot_owner || !MyBots::IsMyBot(bot_owner, bot_owner->GetTarget())) + return nullptr; + + return bot_owner->GetTarget()->CastToBot(); + } + + static Bot* AsNamed_ByBot(Client *bot_owner, std::string bot_name) { + if (!bot_owner || bot_name.empty()) + return nullptr; + + std::list selectable_bot_list; + MyBots::PopulateSBL_BySpawnedBots(bot_owner, selectable_bot_list); + for (auto bot_iter : selectable_bot_list) { + if (!bot_name.compare(bot_iter->GetCleanName())) + return bot_iter; + } + + return nullptr; + } + + static Bot* Select_ByClass(Client* bot_owner, BCEnum::TType target_type, std::list& sbl, uint8 cls, Mob* target_mob = nullptr, bool petless = false) { + if (!bot_owner || sbl.empty()) + return nullptr; + + for (auto bot_iter : sbl) { + if (!MyBots::IsMyBot(bot_owner, bot_iter)) + continue; + if (bot_iter->GetClass() != cls) + continue; + if (petless && bot_iter->GetPet()) + continue; + if (target_type == BCEnum::TT_GroupV1) { + if (!target_mob) + return nullptr; + else if (bot_iter->GetGroup() != target_mob->GetGroup()) + continue; + } + + return bot_iter; + } + + return nullptr; + } + + static Bot* Select_ByMinLevelAndClass(Client* bot_owner, BCEnum::TType target_type, std::list& sbl, uint8 minlvl, uint8 cls, Mob* target_mob = nullptr, bool petless = false) { + if (!bot_owner || sbl.empty()) + return nullptr; + + for (auto bot_iter : sbl) { + if (!MyBots::IsMyBot(bot_owner, bot_iter)) + continue; + if (bot_iter->GetLevel() < minlvl || bot_iter->GetClass() != cls) + continue; + if (petless && bot_iter->GetPet()) + continue; + if (target_type == BCEnum::TT_GroupV1) { + if (!target_mob) + return nullptr; + else if (bot_iter->GetGroup() != target_mob->GetGroup()) + continue; + } + + return bot_iter; + } + + return nullptr; + } + + // Filters actual 'sbl' list + static void Filter_ByClasses(Client* bot_owner, std::list& sbl, uint16 class_mask) { + sbl.remove_if([bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); }); + sbl.remove_if([class_mask](const Bot* l) { return (GetPlayerClassBit(l->GetClass()) & (~class_mask)); }); + } + + static void Filter_ByMinLevel(Client* bot_owner, std::list& sbl, uint8 min_level) { + sbl.remove_if([bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); }); + sbl.remove_if([min_level](const Bot* l) { return (l->GetLevel() < min_level); }); + } + + static void Filter_ByArcher(Client* bot_owner, std::list& sbl) { + sbl.remove_if([bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); }); + sbl.remove_if([bot_owner](Bot* l) { return (!l->IsBotArcher()); }); + } + + static void Filter_ByHighestSkill(Client* bot_owner, std::list& sbl, SkillUseTypes skill_type, float& skill_value) { + sbl.remove_if([bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); }); + skill_value = 0.0f; + + float mod_skill_value = 0.0f; + const Bot* skilled_bot = nullptr; + for (auto bot_iter : sbl) { + float base_skill_value = bot_iter->GetSkill(skill_type); + if (base_skill_value == 0.0f) + continue; + + mod_skill_value = base_skill_value; + for (int16 index = EmuConstants::EQUIPMENT_BEGIN; index <= EmuConstants::EQUIPMENT_END; ++index) { + const ItemInst* indexed_item = bot_iter->GetBotItem(index); + if (indexed_item && indexed_item->GetItem()->SkillModType == skill_type) + mod_skill_value += (base_skill_value * (((float)indexed_item->GetItem()->SkillModValue) / 100.0f)); + } + + if (!skilled_bot) { + skill_value = mod_skill_value; + skilled_bot = bot_iter; + } + else if (mod_skill_value > skill_value) { + skill_value = mod_skill_value; + skilled_bot = bot_iter; + } + } + + sbl.remove_if([skilled_bot](const Bot* l) { return (l != skilled_bot); }); + } + + static void Filter_ByHighestPickLock(Client* bot_owner, std::list& sbl, float& pick_lock_value) { + sbl.remove_if([bot_owner](Bot* l) { return (!MyBots::IsMyBot(bot_owner, l)); }); + sbl.remove_if([bot_owner](const Bot* l) { return (l->GetClass() != ROGUE && l->GetClass() != BARD); }); + sbl.remove_if([bot_owner](const Bot* l) { return (l->GetClass() == ROGUE && l->GetLevel() < 5); }); + sbl.remove_if([bot_owner](const Bot* l) { return (l->GetClass() == BARD && l->GetLevel() < 40); }); + + ActionableBots::Filter_ByHighestSkill(bot_owner, sbl, SkillPickLock, pick_lock_value); + } +}; + + +/* + * bot commands go below here + */ +void bot_command_actionable(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_actionable", sep->arg[0], "actionable")) + return; + + c->Message(m_message, "Actionable command arguments:"); + + c->Message(m_usage, "target: selects target as single bot - use ^command' [target] or imply by empty actionable argument"); + c->Message(m_usage, "byname [name]: selects single bot by name"); + c->Message(m_usage, "ownergroup: selects all bots in the owner's group"); + c->Message(m_usage, "botgroup [name]: NEEDS REWORK???"); + c->Message(m_usage, "targetgroup: selects all bots in target's group"); + c->Message(m_usage, "namesgroup [name]: selects all bots in name's group"); + c->Message(m_usage, "healrotation [name]: selects all member and target bots of a heal rotation where name is a member"); + c->Message(m_usage, "healrotationmembers [name]: selects all member bots of a heal rotation where name is a member"); + c->Message(m_usage, "healrotationtargets [name]: selects all target bots of a heal rotation where name is a member"); + c->Message(m_usage, "spawned: selects all spawned bots"); + c->Message(m_usage, "all: selects all spawned bots - argument use indicates en masse database updating"); + + c->Message(m_message, "You may only select your bots as actionable"); +} + +void bot_command_aggressive(Client *c, const Seperator *sep) +{ + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Stance]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Stance) || helper_command_alias_fail(c, "bot_command_aggressive", sep->arg[0], "aggressive")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotationtargets | spawned] ([actionable_name]))", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Stance); + return; + } + const int ab_mask = ActionableBots::ABM_Type1; + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) + return; + sbl.remove(nullptr); + + int success_count = 0; + int candidate_count = sbl.size(); + for (auto list_iter : *local_list) { + if (sbl.empty()) + break; + + auto local_entry = list_iter->SafeCastToStance(); + if (helper_spell_check_fail(local_entry)) + continue; + if (local_entry->stance_type != BCEnum::StT_Aggressive) + continue; + + for (auto bot_iter = sbl.begin(); bot_iter != sbl.end(); ) { + Bot* my_bot = *bot_iter; + if (local_entry->caster_class != my_bot->GetClass()) { + ++bot_iter; + continue; + } + if (local_entry->spell_level > my_bot->GetLevel()) { + ++bot_iter; + continue; + } + + my_bot->InterruptSpell(); + if (candidate_count == 1) + Bot::BotGroupSay(my_bot, "Using '%s'", spells[local_entry->spell_id].name); + my_bot->UseDiscipline(local_entry->spell_id, my_bot->GetID()); + ++success_count; + + bot_iter = sbl.erase(bot_iter); + } + } + + c->Message(m_action, "%i of %i bots have used aggressive disciplines", success_count, candidate_count); +} + +void bot_command_attack(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_attack", sep->arg[0], "attack")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [actionable: byname | ownergroup | botgroup | namesgroup | healrotation | spawned] ([actionable_name])", sep->arg[0]); + return; + } + const int ab_mask = ActionableBots::ABM_Type2; + + Mob* target_mob = ActionableTarget::AsSingle_ByAttackable(c); + if (!target_mob) { + c->Message(m_fail, "You must an enemy to use this command"); + return; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) + return; + + sbl.remove(nullptr); + for (auto bot_iter : sbl) { + bot_iter->WipeHateList(); + bot_iter->AddToHateList(target_mob, 1); + if (!bot_iter->GetPet()) + continue; + + bot_iter->GetPet()->WipeHateList(); + bot_iter->GetPet()->AddToHateList(target_mob, 1); + } + if (sbl.size() == 1) + Bot::BotGroupSay(sbl.front(), "Attacking %s", target_mob->GetCleanName()); + else + c->Message(m_action, "%i of your bots are attacking %s", sbl.size(), target_mob->GetCleanName()); +} + +void bot_command_bind_affinity(Client *c, const Seperator *sep) +{ + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_BindAffinity]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_BindAffinity) || helper_command_alias_fail(c, "bot_command_bind_affinity", sep->arg[0], "bindaffinity")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_BindAffinity); + return; + } + + ActionableTarget::Types actionable_targets; + Bot* my_bot = nullptr; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + for (auto list_iter : *local_list) { + auto local_entry = list_iter; + if (helper_spell_check_fail(local_entry)) + continue; + + auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); + if (!target_mob) + continue; + + my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); + if (!my_bot) + continue; + + // Cast effect message is not being generated + if (helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id)) + c->Message(m_action, "Successfully bound %s to this location", target_mob->GetCleanName()); + else + c->Message(m_fail, "Failed to bind %s to this location", target_mob->GetCleanName()); + break; + } + + helper_no_available_bots(c, my_bot); +} + +void bot_command_bot(Client *c, const Seperator *sep) +{ + const std::list subcommand_list = { + "botappearance", "botcamp", "botclone", "botcreate", "botdelete", "botdetails", "botdyearmor", "botinspectmessage", "botfollowdistance", + "botlist", "botoutofcombat", "botreport", "botspawn", "botstance", "botsummon", "bottogglearcher", "bottogglehelm", "botupdate" + }; + + if (helper_command_alias_fail(c, "bot_command_bot", sep->arg[0], "bot")) + return; + + helper_send_available_subcommands(c, "bot", subcommand_list); +} + +void bot_command_botgroup(Client *c, const Seperator *sep) +{ + const std::list subcommand_list = { + "botgroupaddmember", "botgroupcreate", "botgroupdelete", "botgrouplist", "botgroupload", "botgroupremovemember" + }; + + if (helper_command_alias_fail(c, "bot_command_botgroup", sep->arg[0], "botgroup")) + return; + + helper_send_available_subcommands(c, "bot-group", subcommand_list); +} + +void bot_command_charm(Client *c, const Seperator *sep) +{ + auto local_list = &bot_command_spells[BCEnum::SpT_Charm]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Charm) || helper_command_alias_fail(c, "bot_command_charm", sep->arg[0], "charm")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s ([option: dire])", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Charm); + return; + } + + bool dire = false; + std::string dire_arg = sep->arg[1]; + if (!dire_arg.compare("dire")) + dire = true; + + ActionableTarget::Types actionable_targets; + Bot* my_bot = nullptr; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + for (auto list_iter : *local_list) { + auto local_entry = list_iter->SafeCastToCharm(); + if (helper_spell_check_fail(local_entry)) + continue; + if (local_entry->dire != dire) + continue; + + auto target_mob = actionable_targets.Select(c, local_entry->target_type, ENEMY); + if (!target_mob) + continue; + if (target_mob->IsCharmed()) { + c->Message(m_fail, "Your is already charmed"); + return; + } + + if (spells[local_entry->spell_id].max[EFFECTIDTOINDEX(1)] < target_mob->GetLevel()) + continue; + + my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob, true); + if (!my_bot) + continue; + + uint32 dont_root_before = 0; + if (helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id, true, &dont_root_before)) + target_mob->SetDontRootMeBefore(dont_root_before); + + break; + } + + helper_no_available_bots(c, my_bot); +} + +void bot_command_cure(Client *c, const Seperator *sep) +{ + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Cure]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Cure) || helper_command_alias_fail(c, "bot_command_cure", sep->arg[0], "cure")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s [ailment: blindness | disease | poison | curse | corruption]", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Cure); + return; + } + + std::string ailment_arg = sep->arg[1]; + + auto ailment_type = BCEnum::AT_None; + if (!ailment_arg.compare("blindness")) + ailment_type = BCEnum::AT_Blindness; + else if (!ailment_arg.compare("disease")) + ailment_type = BCEnum::AT_Disease; + else if (!ailment_arg.compare("poison")) + ailment_type = BCEnum::AT_Poison; + else if (!ailment_arg.compare("curse")) + ailment_type = BCEnum::AT_Curse; + else if (!ailment_arg.compare("corruption")) + ailment_type = BCEnum::AT_Corruption; + + if (ailment_type == BCEnum::AT_None) { + c->Message(m_fail, "You must specify a cure [ailment] to use this command"); + return; + } + + local_list->sort([ailment_type](STBaseEntry* l, STBaseEntry* r) { + auto _l = l->SafeCastToCure(), _r = r->SafeCastToCure(); + if (_l->cure_value[AILMENTIDTOINDEX(ailment_type)] < _r->cure_value[AILMENTIDTOINDEX(ailment_type)]) + return true; + if (_l->cure_value[AILMENTIDTOINDEX(ailment_type)] == _r->cure_value[AILMENTIDTOINDEX(ailment_type)] && spells[_l->spell_id].mana < spells[_r->spell_id].mana) + return true; + if (_l->cure_value[AILMENTIDTOINDEX(ailment_type)] == _r->cure_value[AILMENTIDTOINDEX(ailment_type)] && spells[_l->spell_id].mana == spells[_r->spell_id].mana && _l->cure_total < _r->cure_total) + return true; + + return false; + }); + + ActionableTarget::Types actionable_targets; + Bot* my_bot = nullptr; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + bool cast_success = false; + for (auto list_iter : *local_list) { + auto local_entry = list_iter->SafeCastToCure(); + if (helper_spell_check_fail(local_entry)) + continue; + if (!local_entry->cure_value[AILMENTIDTOINDEX(ailment_type)]) + continue; + + auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); + if (!target_mob) + continue; + + my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); + if (!my_bot) + continue; + + cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); + break; + } + + helper_no_available_bots(c, my_bot); +} + +void bot_command_defensive(Client *c, const Seperator *sep) +{ + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Stance]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Stance) || helper_command_alias_fail(c, "bot_command_defensive", sep->arg[0], "defensive")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotationtargets | spawned] ([actionable_name]))", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Stance); + return; + } + const int ab_mask = ActionableBots::ABM_Type1; + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) + return; + sbl.remove(nullptr); + + int success_count = 0; + int candidate_count = sbl.size(); + for (auto list_iter : *local_list) { + if (sbl.empty()) + break; + + auto local_entry = list_iter->SafeCastToStance(); + if (helper_spell_check_fail(local_entry)) + continue; + if (local_entry->stance_type != BCEnum::StT_Aggressive) + continue; + + for (auto bot_iter = sbl.begin(); bot_iter != sbl.end(); ) { + Bot* my_bot = *bot_iter; + if (local_entry->caster_class != my_bot->GetClass()) { + ++bot_iter; + continue; + } + if (local_entry->spell_level > my_bot->GetLevel()) { + ++bot_iter; + continue; + } + + my_bot->InterruptSpell(); + if (candidate_count == 1) + Bot::BotGroupSay(my_bot, "Using '%s'", spells[local_entry->spell_id].name); + my_bot->UseDiscipline(local_entry->spell_id, my_bot->GetID()); + ++success_count; + + bot_iter = sbl.erase(bot_iter); + } + } + + c->Message(m_action, "%i of %i bots have used defensive disciplines", success_count, candidate_count); +} + +void bot_command_depart(Client *c, const Seperator *sep) +{ + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Depart]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Depart) || helper_command_alias_fail(c, "bot_command_depart", sep->arg[0], "depart")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [list | destination] ([option: single])", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Depart); + return; + } + + bool single = false; + std::string single_arg = sep->arg[2]; + if (!single_arg.compare("single")) + single = true; + + std::string destination = sep->arg[1]; + if (!destination.compare("list")) { + Bot* my_druid_bot = ActionableBots::AsGroupMember_ByClass(c, c, DRUID); + Bot* my_wizard_bot = ActionableBots::AsGroupMember_ByClass(c, c, WIZARD); + helper_command_depart_list(c, my_druid_bot, my_wizard_bot, local_list, single); + return; + } + else if (destination.empty()) { + c->Message(m_fail, "A [destination] or [list] argument is required to use this command"); + return; + } + + ActionableTarget::Types actionable_targets; + Bot* my_bot = nullptr; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + bool cast_success = false; + for (auto list_iter : *local_list) { + auto local_entry = list_iter->SafeCastToDepart(); + if (helper_spell_check_fail(local_entry)) + continue; + if (local_entry->single != single) + continue; + if (destination.compare(spells[local_entry->spell_id].teleport_zone)) + continue; + + auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); + if (!target_mob) + continue; + + my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); + if (!my_bot) + continue; + + cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); + break; + } + + helper_no_available_bots(c, my_bot); +} + +void bot_command_escape(Client *c, const Seperator *sep) +{ + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Escape]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Escape) || helper_command_alias_fail(c, "bot_command_escape", sep->arg[0], "escape")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s ([option: lesser])", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Escape); + return; + } + + bool use_lesser = false; + if (!strcasecmp(sep->arg[1], "lesser")) + use_lesser = true; + + ActionableTarget::Types actionable_targets; + Bot* my_bot = nullptr; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + bool cast_success = false; + for (auto list_iter : *local_list) { + auto local_entry = list_iter->SafeCastToEscape(); + if (helper_spell_check_fail(local_entry)) + continue; + if (local_entry->lesser != use_lesser) + continue; + + auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); + if (!target_mob) + continue; + + my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); + if (!my_bot) + continue; + + cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); + break; + } + + helper_no_available_bots(c, my_bot); +} + +void bot_command_find_aliases(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_find_aliases", sep->arg[0], "findaliases")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [alias | command]", sep->arg[0]); + return; + } + + auto find_iter = bot_command_aliases.find(sep->arg[1]); + if (find_iter == bot_command_aliases.end()) { + c->Message(m_fail, "No bot commands or aliases match '%s'", sep->arg[1]); + return; + } + + auto command_iter = bot_command_list.find(find_iter->second); + if (find_iter->second.empty() || command_iter == bot_command_list.end()) { + c->Message(m_unknown, "An unknown condition has occurred..."); + return; + } + + c->Message(m_message, "Available bot command aliases for '%s':", command_iter->first.c_str()); + + int bot_command_aliases_shown = 0; + for (auto alias_iter : bot_command_aliases) { + if (strcasecmp(find_iter->second.c_str(), alias_iter.second.c_str()) || c->Admin() < command_iter->second->access) + continue; + + c->Message(m_usage, "%c%s", BOT_COMMAND_CHAR, alias_iter.first.c_str()); + ++bot_command_aliases_shown; + } + c->Message(m_message, "%d bot command alias%s listed.", bot_command_aliases_shown, bot_command_aliases_shown != 1 ? "es" : ""); +} + +void bot_command_follow(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_follow", sep->arg[0], "follow")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s ([option: reset]) [actionable: byname | ownergroup | botgroup | namesgroup | healrotation | spawned] ([actionable_name])", sep->arg[0]); + return; + } + const int ab_mask = ActionableBots::ABM_Type2; + + bool reset = false; + int ab_arg = 1; + int name_arg = 2; + Mob* target_mob = nullptr; + + std::string reset_arg = sep->arg[1]; + if (!reset_arg.compare("reset")) { + reset = true; + ab_arg = 2; + name_arg = 3; + } + else { + if (c->GetTarget()) { + if (c->IsAttackAllowed(c->GetTarget())) { + c->Message(m_fail, "You must a friendly mob to use this command"); + return; + } + target_mob = c->GetTarget(); + } + else { + target_mob = c; + } + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, sep->arg[name_arg]) == ActionableBots::ABT_None) + return; + + sbl.remove(nullptr); + for (auto bot_iter : sbl) { + bot_iter->WipeHateList(); + auto my_group = bot_iter->GetGroup(); + if (my_group) { + if (reset) { + if (!my_group->GetLeader() || my_group->GetLeader() == bot_iter) + bot_iter->SetFollowID(c->GetID()); + else + bot_iter->SetFollowID(my_group->GetLeader()->GetID()); + } + else { + bot_iter->SetFollowID(target_mob->GetID()); + } + } + else { + bot_iter->SetFollowID(0); + } + if (!bot_iter->GetPet()) + continue; + + bot_iter->GetPet()->WipeHateList(); + bot_iter->GetPet()->SetFollowID(bot_iter->GetID()); + } + if (sbl.size() == 1) { + Mob* follow_mob = entity_list.GetMob(sbl.front()->GetFollowID()); + Bot::BotGroupSay(sbl.front(), "Following %s", ((follow_mob) ? (follow_mob->GetCleanName()) : ("'nullptr'"))); + } + else { + if (reset) + c->Message(m_action, "%i of your bots are following their default assignments", sbl.size()); + else + c->Message(m_action, "%i of your bots are following %s", sbl.size(), target_mob->GetCleanName()); + } +} + +void bot_command_guard(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_guard", sep->arg[0], "guard")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [actionable: target | byname | ownergroup | botgroup | namesgroup | healrotation | spawned] ([actionable_name])", sep->arg[0]); + return; + } + const int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_Type2); + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) + return; + + sbl.remove(nullptr); + for (auto bot_iter : sbl) { + bot_iter->WipeHateList(); + bot_iter->SetFollowID(0); + if (!bot_iter->GetPet()) + continue; + + bot_iter->GetPet()->WipeHateList(); + bot_iter->GetPet()->SetFollowID(0); + } + if (sbl.size() == 1) + Bot::BotGroupSay(sbl.front(), "Guarding this position"); + else + c->Message(m_action, "%i of your bots are guarding their positions", sbl.size()); +} + +void bot_command_heal_rotation(Client *c, const Seperator *sep) +{ + const std::list subcommand_list = { + "healrotationadaptivetargeting", "healrotationaddmember", "healrotationaddtarget", "healrotationadjustcritical", "healrotationadjustsafe", + "healrotationcastoverride", "healrotationchangeinterval", "healrotationcleartargets", "healrotationcreate", "healrotationfastheals", + "healrotationlist", "healrotationremovemember", "healrotationremovetarget", "healrotationresetlimits", "healrotationstart", "healrotationstop" + }; + + if (helper_command_alias_fail(c, "bot_command_heal_rotation", sep->arg[0], "healrotation")) + return; + +#if (EQDEBUG >= 12) + while (c->Admin() >= 250) { + if (strcasecmp(sep->arg[1], "shone")) { break; } + Bot* my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot || !(my_bot->IsHealRotationMember())) { break; } + auto tlist = (*my_bot->MemberOfHealRotation())->TargetList(); + if (tlist->empty()) { break; } + for (auto tlist_iter : *tlist) { + if (tlist_iter) + tlist_iter->SetHP((tlist_iter->GetMaxHP() / 100 + 1)); + } + return; + } +#endif + + helper_send_available_subcommands(c, "bot heal rotation", subcommand_list); +} + +void bot_command_help(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_help", sep->arg[0], "help")) + return; + + c->Message(m_message, "Available EQEMu bot commands:"); + + int bot_commands_shown = 0; + for (auto command_iter : bot_command_list) { + if (sep->arg[1][0] && command_iter.first.find(sep->arg[1]) == std::string::npos) + continue; + if (c->Admin() < command_iter.second->access) + continue; + + c->Message(m_usage, "%c%s - %s", BOT_COMMAND_CHAR, command_iter.first.c_str(), command_iter.second->desc == nullptr ? "[no description]" : command_iter.second->desc); + ++bot_commands_shown; + } + c->Message(m_message, "%d bot command%s listed.", bot_commands_shown, bot_commands_shown != 1 ? "s" : ""); + c->Message(m_note, "type %ccommand [help | usage] for more information", BOT_COMMAND_CHAR); +} + +void bot_command_hold(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_hold", sep->arg[0], "hold")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s ([actionable: ] ([actionable_name]))", sep->arg[0]); + return; + } + const int ab_mask = ActionableBots::ABM_NoFilter; + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) + return; + + sbl.remove(nullptr); + for (auto bot_iter : sbl) + bot_iter->SetPauseAI(true); + + c->Message(m_action, "%i of your bots %s suspended", sbl.size(), ((sbl.size() != 1) ? ("are") : ("is"))); +} + +void bot_command_identify(Client *c, const Seperator *sep) +{ + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Identify]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Identify) || helper_command_alias_fail(c, "bot_command_identify", sep->arg[0], "identify")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Identify); + return; + } + + ActionableTarget::Types actionable_targets; + Bot* my_bot = nullptr; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + bool cast_success = false; + for (auto list_iter : *local_list) { + auto local_entry = list_iter; + if (helper_spell_check_fail(local_entry)) + continue; + + auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); + if (!target_mob) + continue; + + my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); + if (!my_bot) + continue; + + cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); + break; + } + + helper_no_available_bots(c, my_bot); +} + +void bot_command_inventory(Client *c, const Seperator *sep) +{ + const std::list subcommand_list = { "inventorygive", "inventorylist", "inventoryremove" }; + + if (helper_command_alias_fail(c, "bot_command_inventory", sep->arg[0], "inventory")) + return; + + helper_send_available_subcommands(c, "bot inventory", subcommand_list); +} + +void bot_command_invisibility(Client *c, const Seperator *sep) +{ + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Invisibility]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Invisibility) || helper_command_alias_fail(c, "bot_command_invisibility", sep->arg[0], "invisibility")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s [invisibility: living | undead | animal | see]", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Invisibility); + return; + } + + std::string invisibility = sep->arg[1]; + + BCEnum::IType invisibility_type = BCEnum::IT_None; + if (!invisibility.compare("living")) + invisibility_type = BCEnum::IT_Living; + else if (!invisibility.compare("undead")) + invisibility_type = BCEnum::IT_Undead; + else if (!invisibility.compare("animal")) + invisibility_type = BCEnum::IT_Animal; + else if (!invisibility.compare("see")) + invisibility_type = BCEnum::IT_See; + + if (invisibility_type == BCEnum::IT_None) { + c->Message(m_fail, "You must specify an [invisibility]"); + return; + } + + ActionableTarget::Types actionable_targets; + Bot* my_bot = nullptr; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + bool cast_success = false; + for (auto list_iter : *local_list) { + auto local_entry = list_iter->SafeCastToInvisibility(); + if (helper_spell_check_fail(local_entry)) + continue; + if (local_entry->invis_type != invisibility_type) + continue; + + auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); + if (!target_mob) + continue; + + my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); + if (!my_bot) + continue; + + cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); + break; + } + + helper_no_available_bots(c, my_bot); +} + +void bot_command_levitation(Client *c, const Seperator *sep) +{ + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Levitation]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Levitation) || helper_command_alias_fail(c, "bot_command_levitation", sep->arg[0], "levitation")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Levitation); + return; + } + + ActionableTarget::Types actionable_targets; + Bot* my_bot = nullptr; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + bool cast_success = false; + for (auto list_iter : *local_list) { + auto local_entry = list_iter; + if (helper_spell_check_fail(local_entry)) + continue; + + auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); + if (!target_mob) + continue; + + my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); + if (!my_bot) + continue; + + cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); + break; + } + + helper_no_available_bots(c, my_bot); +} + +void bot_command_lull(Client *c, const Seperator *sep) +{ + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Lull]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Lull) || helper_command_alias_fail(c, "bot_command_lull", sep->arg[0], "lull")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Lull); + return; + } + + ActionableTarget::Types actionable_targets; + Bot* my_bot = nullptr; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + for (auto list_iter : *local_list) { + auto local_entry = list_iter; + if (helper_spell_check_fail(local_entry)) + continue; + + auto target_mob = actionable_targets.Select(c, local_entry->target_type, ENEMY); + if (!target_mob) + continue; + + //if (spells[local_entry->spell_id].max[EFFECTIDTOINDEX(3)] && spells[local_entry->spell_id].max[EFFECTIDTOINDEX(3)] < target_mob->GetLevel()) + // continue; + + my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); + if (!my_bot) + continue; + + uint32 dont_root_before = 0; + if (helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id, true, &dont_root_before)) + target_mob->SetDontRootMeBefore(dont_root_before); + + break; + } + + helper_no_available_bots(c, my_bot); +} + +void bot_command_mesmerize(Client *c, const Seperator *sep) +{ + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Mesmerize]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Mesmerize) || helper_command_alias_fail(c, "bot_command_mesmerize", sep->arg[0], "mesmerize")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Mesmerize); + return; + } + + ActionableTarget::Types actionable_targets; + Bot* my_bot = nullptr; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + for (auto list_iter : *local_list) { + auto local_entry = list_iter; + if (helper_spell_check_fail(local_entry)) + continue; + + auto target_mob = actionable_targets.Select(c, local_entry->target_type, ENEMY); + if (!target_mob) + continue; + + if (spells[local_entry->spell_id].max[EFFECTIDTOINDEX(1)] < target_mob->GetLevel()) + continue; + + my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); + if (!my_bot) + continue; + + uint32 dont_root_before = 0; + if (helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id, true, &dont_root_before)) + target_mob->SetDontRootMeBefore(dont_root_before); + + break; + } + + helper_no_available_bots(c, my_bot); +} + +void bot_command_movement_speed(Client *c, const Seperator *sep) +{ + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_MovementSpeed]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_MovementSpeed) || helper_command_alias_fail(c, "bot_command_movement_speed", sep->arg[0], "movementspeed")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s ([group])", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_MovementSpeed); + return; + } + + bool group = false; + std::string group_arg = sep->arg[1]; + if (!group_arg.compare("group")) + group = true; + + ActionableTarget::Types actionable_targets; + Bot* my_bot = nullptr; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + bool cast_success = false; + for (auto list_iter : *local_list) { + auto local_entry = list_iter->SafeCastToMovementSpeed(); + if (helper_spell_check_fail(local_entry)) + continue; + if (local_entry->group != group) + continue; + + auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); + if (!target_mob) + continue; + + my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); + if (!my_bot) + continue; + + cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); + break; + } + + helper_no_available_bots(c, my_bot); +} + +void bot_command_pet(Client *c, const Seperator *sep) +{ + const std::list subcommand_list = { "petremove", "petsettype" }; + + if (helper_command_alias_fail(c, "bot_command_pet", sep->arg[0], "pet")) + return; + + helper_send_available_subcommands(c, "bot pet", subcommand_list); +} + +void bot_command_pick_lock(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_pick_lock", sep->arg[0], "picklock")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s", sep->arg[0]); + c->Message(m_note, "requires one of the following bot classes:"); + c->Message(m_message, "Rogue(5) or Bard(40)"); + return; + } + + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + float pick_lock_value = 0.0f; + ActionableBots::Filter_ByHighestPickLock(c, sbl, pick_lock_value); + if (sbl.empty()) { + c->Message(m_fail, "No bots are capable of performing this action"); + return; + } + + Bot* my_bot = sbl.front(); + + my_bot->InterruptSpell(); + Bot::BotGroupSay(my_bot, "Attempting to pick the lock.."); + + std::list door_list; + entity_list.GetDoorsList(door_list); + + int door_count = 0, open_count = 0; + for (auto door_iter : door_list) { + if (!door_iter || door_iter->IsDoorOpen()) + continue; + + glm::tvec4 diff = (c->GetPosition() - door_iter->GetPosition()); + + float curdist = ((diff.x * diff.x) + (diff.y * diff.y)); + float curelev = (diff.z * diff.z); +#if (EQDEBUG >= 11) + if (curdist <= 130 && curelev <= 65 && curelev >= 25) // 2D limit is '130' (x^2 + y^2), 1D theoretically should be '65' (z^2) + Log.Out(Logs::Detail, Logs::Doors, "bot_command_pick_lock(): DoorID: %i - Elevation difference failure within theoretical limit (%f <= 65.0)", door_iter->GetDoorID(), curelev); +#endif + if (curelev >= 25 || curdist > 130) // changed curelev from '10' to '25' - requiring diff.z to be less than '5' + continue; + + ++door_count; + if (pick_lock_value >= door_iter->GetLockpick()) { + door_iter->ForceOpen(my_bot); + ++open_count; + } + else { + Bot::BotGroupSay(my_bot, "I am not skilled enough for this lock..."); + } + } + c->Message(m_action, "%i door%s attempted - %i door%s successful", door_count, ((door_count != 1) ? ("s") : ("")), open_count, ((open_count != 1) ? ("s") : (""))); +} + +void bot_command_pull(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_pull", sep->arg[0], "pull")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s", sep->arg[0]); + return; + } + int ab_mask = ActionableBots::ABM_OwnerGroup; // existing behavior - need to add c->IsGrouped() check and modify code if different behavior is desired + + std::list sbl; + if (ActionableBots::PopulateSBL(c, "ownergroup", sbl, ab_mask) == ActionableBots::ABT_None) + return; + sbl.remove(nullptr); + + auto target_mob = ActionableTarget::VerifyEnemy(c, BCEnum::TT_Single); + if (!target_mob) { + c->Message(m_fail, "Your current target is not attackable"); + return; + } + + Bot* bot_puller = nullptr; + for (auto bot_iter : sbl) { + if (!bot_iter->IsArcheryRange(target_mob)) + continue; + + Bot::BotGroupSay(bot_iter, "Attempting to pull %s..", target_mob->GetCleanName()); + bot_iter->InterruptSpell(); + bot_iter->BotRangedAttack(target_mob); + bot_puller = bot_iter; + break; + } + + helper_no_available_bots(c, bot_puller); +} + +void bot_command_release(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_release", sep->arg[0], "release")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s ([actionable: ] ([actionable_name]))", sep->arg[0]); + return; + } + const int ab_mask = ActionableBots::ABM_NoFilter; + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) + return; + + sbl.remove(nullptr); + for (auto bot_iter : sbl) { + bot_iter->WipeHateList(); + bot_iter->SetPauseAI(false); + } + + c->Message(m_action, "%i of your bots %s unsuspended", sbl.size(), ((sbl.size() != 1) ? ("are") : ("is"))); +} + +void bot_command_resistance(Client *c, const Seperator *sep) +{ + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Resistance]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Resistance) || helper_command_alias_fail(c, "bot_command_resistance", sep->arg[0], "resistance")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s [resistance: fire | cold | poison | disease | magic | corruption]", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Resistance); + return; + } + + std::string resistance_arg = sep->arg[1]; + + auto resistance_type = BCEnum::RT_None; + if (!resistance_arg.compare("fire")) + resistance_type = BCEnum::RT_Fire; + else if (!resistance_arg.compare("cold")) + resistance_type = BCEnum::RT_Cold; + else if (!resistance_arg.compare("poison")) + resistance_type = BCEnum::RT_Poison; + else if (!resistance_arg.compare("disease")) + resistance_type = BCEnum::RT_Disease; + else if (!resistance_arg.compare("magic")) + resistance_type = BCEnum::RT_Magic; + else if (!resistance_arg.compare("corruption")) + resistance_type = BCEnum::RT_Corruption; + + if (resistance_type == BCEnum::RT_None) { + c->Message(m_fail, "You must specify a [resistance]"); + return; + } + + local_list->sort([resistance_type](STBaseEntry* l, STBaseEntry* r) { + auto _l = l->SafeCastToResistance(), _r = r->SafeCastToResistance(); + if (_l->resist_value[RESISTANCEIDTOINDEX(resistance_type)] > _r->resist_value[RESISTANCEIDTOINDEX(resistance_type)]) + return true; + if (_l->resist_value[RESISTANCEIDTOINDEX(resistance_type)] == _r->resist_value[RESISTANCEIDTOINDEX(resistance_type)] && spells[_l->spell_id].mana < spells[_r->spell_id].mana) + return true; + if (_l->resist_value[RESISTANCEIDTOINDEX(resistance_type)] == _r->resist_value[RESISTANCEIDTOINDEX(resistance_type)] && spells[_l->spell_id].mana == spells[_r->spell_id].mana && _l->resist_total > _r->resist_total) + return true; + + return false; + }); + + ActionableTarget::Types actionable_targets; + Bot* my_bot = nullptr; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + bool cast_success = false; + for (auto list_iter : *local_list) { + auto local_entry = list_iter->SafeCastToResistance(); + if (helper_spell_check_fail(local_entry)) + continue; + if (!local_entry->resist_value[RESISTANCEIDTOINDEX(resistance_type)]) + continue; + + auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); + if (!target_mob) + continue; + + my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); + if (!my_bot) + continue; + + cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); + break; + } + + helper_no_available_bots(c, my_bot); +} + +void bot_command_resurrect(Client *c, const Seperator *sep) +{ + // Obscure bot spell code prohibits the aoe portion from working correctly... + + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Resurrect]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Resurrect) || helper_command_alias_fail(c, "bot_command_resurrect", sep->arg[0], "resurrect")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + //c->Message(m_usage, "usage: %s ([option: aoe])", sep->arg[0]); + c->Message(m_usage, "usage: %s", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Resurrect); + return; + } + + bool aoe = false; + //std::string aoe_arg = sep->arg[1]; + //if (!aoe_arg.compare("aoe")) + // aoe = true; + + ActionableTarget::Types actionable_targets; + Bot* my_bot = nullptr; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + for (auto list_iter : *local_list) { + auto local_entry = list_iter->SafeCastToResurrect(); + if (helper_spell_check_fail(local_entry)) + continue; + //if (local_entry->aoe != aoe) + // continue; + if (local_entry->aoe) + continue; + + auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); + //if (!target_mob && !local_entry->aoe) + // continue; + if (!target_mob) + continue; + + my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); + if (!my_bot) + continue; + + //if (local_entry->aoe) + // target_mob = my_bot; + + uint32 dont_root_before = 0; + if (helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id, true, &dont_root_before)) + target_mob->SetDontRootMeBefore(dont_root_before); + + break; + } + + helper_no_available_bots(c, my_bot); +} + +void bot_command_rune(Client *c, const Seperator *sep) +{ + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Rune]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Rune) || helper_command_alias_fail(c, "bot_command_rune", sep->arg[0], "rune")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Rune); + return; + } + + ActionableTarget::Types actionable_targets; + Bot* my_bot = nullptr; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + bool cast_success = false; + for (auto list_iter : *local_list) { + auto local_entry = list_iter; + if (helper_spell_check_fail(local_entry)) + continue; + + auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); + if (!target_mob) + continue; + + my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); + if (!my_bot) + continue; + + cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); + break; + } + + helper_no_available_bots(c, my_bot); +} + +void bot_command_send_home(Client *c, const Seperator *sep) +{ + // Obscure bot spell code prohibits the aoe portion from working correctly... + + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_SendHome]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_SendHome) || helper_command_alias_fail(c, "bot_command_send_home", sep->arg[0], "sendhome")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s ([option: group])", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_SendHome); + return; + } + + bool group = false; + std::string group_arg = sep->arg[1]; + if (!group_arg.compare("group")) + group = true; + + ActionableTarget::Types actionable_targets; + Bot* my_bot = nullptr; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + bool cast_success = false; + for (auto list_iter : *local_list) { + auto local_entry = list_iter->SafeCastToSendHome(); + if (helper_spell_check_fail(local_entry)) + continue; + if (local_entry->group != group) + continue; + + auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); + if (!target_mob) + continue; + + my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); + if (!my_bot) + continue; + + cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); + break; + } + + helper_no_available_bots(c, my_bot); +} + +void bot_command_size(Client *c, const Seperator *sep) +{ + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Size]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Size) || helper_command_alias_fail(c, "bot_command_size", sep->arg[0], "size")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s [grow | shrink]", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Size); + return; + } + + std::string size_arg = sep->arg[1]; + auto size_type = BCEnum::SzT_Reduce; + if (!size_arg.compare("grow")) { + size_type = BCEnum::SzT_Enlarge; + } + else if (size_arg.compare("shrink")) { + c->Message(m_fail, "This command requires a [grow | shrink] argument"); + return; + } + + ActionableTarget::Types actionable_targets; + Bot* my_bot = nullptr; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + bool cast_success = false; + for (auto list_iter : *local_list) { + auto local_entry = list_iter->SafeCastToSize(); + if (helper_spell_check_fail(local_entry)) + continue; + if (local_entry->size_type != size_type) + continue; + + auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); + if (!target_mob) + continue; + + my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); + if (!my_bot) + continue; + + cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); + break; + } + + helper_no_available_bots(c, my_bot); +} + +void bot_command_summon_corpse(Client *c, const Seperator *sep) +{ + // Same methodology as old command..but, does not appear to work... (note: didn't work there, either...) + + // temp + c->Message(m_fail, "This command is currently unavailable..."); + return; + + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_SummonCorpse]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_SummonCorpse) || helper_command_alias_fail(c, "bot_command_summon_corpse", sep->arg[0], "summoncorpse")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_SummonCorpse); + return; + } + + Bot* my_bot = nullptr; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + bool cast_success = false; + for (auto list_iter : *local_list) { + auto local_entry = list_iter; + if (helper_spell_check_fail(local_entry)) + continue; + + auto target_mob = ActionableTarget::AsSingle_ByPlayer(c); + if (!target_mob) + continue; + + if (spells[local_entry->spell_id].base[EFFECTIDTOINDEX(1)] < target_mob->GetLevel()) + continue; + + my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); + if (!my_bot) + continue; + + cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); + + break; + } + + helper_no_available_bots(c, my_bot); +} + +void bot_command_taunt(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_taunt", sep->arg[0], "taunt")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s ([option: on | off]) ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotationtargets | spawned] ([actionable_name]))", sep->arg[0]); + return; + } + const int ab_mask = ActionableBots::ABM_Type1; + + std::string arg1 = sep->arg[1]; + + bool taunt_state = false; + bool toggle_taunt = true; + int ab_arg = 1; + if (!arg1.compare("on")) { + taunt_state = true; + toggle_taunt = false; + ab_arg = 2; + } + else if (!arg1.compare("off")) { + toggle_taunt = false; + ab_arg = 2; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, sep->arg[(ab_arg + 1)]) == ActionableBots::ABT_None) + return; + sbl.remove(nullptr); + + int taunting_count = 0; + for (auto bot_iter : sbl) { + if (!bot_iter->GetSkill(SkillTaunt)) + continue; + + if (toggle_taunt) + bot_iter->SetTaunting(!bot_iter->IsTaunting()); + else + bot_iter->SetTaunting(taunt_state); + + if (sbl.size() == 1) + Bot::BotGroupSay(bot_iter, "I am %s taunting", bot_iter->IsTaunting() ? "now" : "no longer"); + + ++taunting_count; + } + + if (taunting_count) { + if (toggle_taunt) + c->Message(m_action, "%i of your bots %s toggled their taunting state", taunting_count, ((taunting_count != 1) ? ("have") : ("has"))); + else + c->Message(m_action, "%i of your bots %s %s taunting", taunting_count, ((taunting_count != 1) ? ("have") : ("has")), ((taunt_state) ? ("started") : ("stopped"))); + } + else { + c->Message(m_fail, "None of your bots are able to taunt"); + } +} + +void bot_command_track(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_track", sep->arg[0], "track")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s (Ranger: [option=all: all | rare | local])", sep->arg[0]); + c->Message(m_note, "requires one of the following bot classes:"); + c->Message(m_message, "Ranger(1), Druid(20) or Bard(35)"); + return; + } + + std::string tracking_scope = sep->arg[1]; + + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + uint16 class_mask = (PLAYER_CLASS_RANGER_BIT | PLAYER_CLASS_DRUID_BIT | PLAYER_CLASS_BARD_BIT); + ActionableBots::Filter_ByClasses(c, sbl, class_mask); + + Bot* my_bot = ActionableBots::AsSpawned_ByMinLevelAndClass(c, sbl, 1, RANGER); + if (tracking_scope.empty()) { + if (!my_bot) + my_bot = ActionableBots::AsSpawned_ByMinLevelAndClass(c, sbl, 20, DRUID); + if (!my_bot) + my_bot = ActionableBots::AsSpawned_ByMinLevelAndClass(c, sbl, 35, BARD); + } + if (!my_bot) { + c->Message(m_fail, "No bots are capable of performing this action"); + return; + } + + int base_distance = 0; + bool track_named = false; + std::string tracking_msg; + switch (my_bot->GetClass()) { + case RANGER: + if (!tracking_scope.compare("local")) { + base_distance = 30; + tracking_msg = "Local tracking..."; + } + else if (!tracking_scope.compare("rare")) { + base_distance = 80; + bool track_named = true; + tracking_msg = "Master tracking..."; + } + else { // default to 'all' + base_distance = 80; + tracking_msg = "Advanced tracking..."; + } + break; + case DRUID: + base_distance = 30; + tracking_msg = "Local tracking..."; + break; + case BARD: + base_distance = 20; + tracking_msg = "Near tracking..."; + break; + default: + return; + } + if (!base_distance) { + c->Message(m_unknown, "An unknown codition has occurred"); + return; + } + + my_bot->InterruptSpell(); + Bot::BotGroupSay(my_bot, tracking_msg.c_str()); + entity_list.ShowSpawnWindow(c, (c->GetLevel() * base_distance), track_named); +} + +void bot_command_water_breathing(Client *c, const Seperator *sep) +{ + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_WaterBreathing]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_WaterBreathing) || helper_command_alias_fail(c, "bot_command_water_breathing", sep->arg[0], "waterbreathing")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_WaterBreathing); + return; + } + + ActionableTarget::Types actionable_targets; + Bot* my_bot = nullptr; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + bool cast_success = false; + for (auto list_iter : *local_list) { + auto local_entry = list_iter; + if (helper_spell_check_fail(local_entry)) + continue; + + auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); + if (!target_mob) + continue; + + my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); + if (!my_bot) + continue; + + cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); + break; + } + + helper_no_available_bots(c, my_bot); +} + + +/* + * bot subcommands go below here + */ +void bot_subcommand_bot_appearance(Client *c, const Seperator *sep) +{ + const std::list subcommand_list = { + "botbeardcolor", "botbeardstyle", "botdetails", "boteyes", "botface", + "bothaircolor", "bothairstyle", "botheritage", "bottattoo", "botwoad" + }; + + if (helper_command_alias_fail(c, "bot_subcommand_bot_appearance", sep->arg[0], "botappearance")) + return; + + helper_send_available_subcommands(c, "bot appearance", subcommand_list); +} + +void bot_subcommand_bot_beard_color(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_bot_beard_color", sep->arg[0], "botbeardcolor")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [value: 0-n] (Dwarves or male bots only)", sep->arg[0]); + c->Message(m_note, "note: Actual limit is filter-based"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(m_fail, "You must a bot that you own to use this command"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(m_fail, "A numeric [value] is required to use this command"); + return; + } + + uint8 uvalue = atoi(sep->arg[1]); + + auto fail_type = BCEnum::AFT_None; + if (my_bot->GetGender() != MALE && my_bot->GetRace() != DWARF) + fail_type = BCEnum::AFT_GenderRace; + else if (!PlayerAppearance::IsValidBeardColor(my_bot->GetRace(), my_bot->GetGender(), uvalue)) + fail_type = BCEnum::AFT_Value; + else + my_bot->SetBeardColor(uvalue); + + if (helper_bot_appearance_fail(c, my_bot, fail_type, "beard color")) + return; + + helper_bot_appearance_form_final(c, my_bot); +} + +void bot_subcommand_bot_beard_style(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_bot_beard_style", sep->arg[0], "botbeardstyle")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [value: 0-n] (Dwarves or male bots only)", sep->arg[0]); + c->Message(m_note, "note: Actual limit is filter-based"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(m_fail, "You must a bot that you own to use this command"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(m_fail, "A numeric [value] is required to use this command"); + return; + } + + uint8 uvalue = atoi(sep->arg[1]); + + auto fail_type = BCEnum::AFT_None; + if (my_bot->GetGender() != MALE && my_bot->GetRace() != DWARF) + fail_type = BCEnum::AFT_GenderRace; + else if (!PlayerAppearance::IsValidBeard(my_bot->GetRace(), my_bot->GetGender(), uvalue)) + fail_type = BCEnum::AFT_Value; + else + my_bot->SetBeard(uvalue); + + if (helper_bot_appearance_fail(c, my_bot, fail_type, "beard style")) + return; + + helper_bot_appearance_form_final(c, my_bot); +} + +void bot_subcommand_bot_camp(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_bot_camp", sep->arg[0], "botcamp")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + return; + } + const int ab_mask = ActionableBots::ABM_NoFilter; + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) + return; + + for (auto bot_iter : sbl) + bot_iter->Camp(); +} + +void bot_subcommand_bot_clone(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_bot_clone", sep->arg[0], "botclone")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [clone_name]", sep->arg[0]); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(m_fail, "You must a bot that you own to use this command"); + return; + } + if (!my_bot->GetBotID()) { + c->Message(m_unknown, "An unknown error has occured - BotName: %s, BotID: %u", my_bot->GetCleanName(), my_bot->GetBotID()); + Log.Out(Logs::General, Logs::Commands, "bot_command_clone(): - Error: Active bot reported invalid ID (BotName: %s, BotID: %u, OwnerName: %s, OwnerID: %u, AcctName: %s, AcctID: %u)", + my_bot->GetCleanName(), my_bot->GetBotID(), c->GetCleanName(), c->CharacterID(), c->AccountName(), c->AccountID()); + return; + } + + if (sep->arg[1][0] == '\0' || sep->IsNumber(1)) { + c->Message(m_fail, "You must [name] your bot clone"); + return; + } + std::string bot_name = sep->arg[1]; + + if (!Bot::IsValidName(bot_name)) { + c->Message(m_fail, "'%s' is an invalid name. You may only use characters 'A-Z', 'a-z' and '_'", bot_name.c_str()); + return; + } + + std::string TempErrorMessage; + + if (!Bot::IsBotNameAvailable(bot_name.c_str(), &TempErrorMessage)) { + if (!TempErrorMessage.empty()) + c->Message(m_unknown, "Database Error: %s", TempErrorMessage.c_str()); + c->Message(m_fail, "The name %s is already being used. Please choose a different name", bot_name.c_str()); + return; + } + + uint32 mbc = RuleI(Bots, CreationLimit); + if (Bot::CreatedBotCount(c->CharacterID(), &TempErrorMessage) >= mbc) { + if (!TempErrorMessage.empty()) + c->Message(m_unknown, "Database Error: %s", TempErrorMessage.c_str()); + c->Message(m_fail, "You have reached the maximum limit of %i bots", mbc); + return; + } + + auto clone_id = botdb.Clone(c->CharacterID(), my_bot->GetBotID(), bot_name.c_str()); + if (!clone_id) { + c->Message(m_fail, "Clone creation of bot '%s' failed...", my_bot->GetCleanName()); + return; + } + + if (!botdb.CloneInventory(c->CharacterID(), my_bot->GetBotID(), clone_id)) { + c->Message(m_fail, "Inventory import for bot clone '%s' failed...", bot_name.c_str()); + return; + } + + c->Message(m_action, "Bot '%s' was successfully cloned to bot '%s'", my_bot->GetCleanName(), bot_name.c_str()); +} + +void bot_subcommand_bot_create(Client *c, const Seperator *sep) +{ + const std::string msg_class = StringFormat("class: %u(WAR), %u(CLR), %u(PAL), %u(RNG), %u(SHD), %u(DRU), %u(MNK), %u(BRD), %u(ROG), %u(SHM), %u(NEC), %u(WIZ), %u(MAG), %u(ENC), %u(BST), %u(BER)", + WARRIOR, CLERIC, PALADIN, RANGER, SHADOWKNIGHT, DRUID, MONK, BARD, ROGUE, SHAMAN, NECROMANCER, WIZARD, MAGICIAN, ENCHANTER, BEASTLORD, BERSERKER); + const std::string msg_race = StringFormat("race: %u(HUM), %u(BAR), %u(ERU), %u(ELF), %u(HIE), %u(DEF), %u(HEF), %u(DWF), %u(TRL), %u(OGR), %u(HFL), %u(GNM), %u(IKS), %u(VAH), %u(FRG), %u(DRK)", + HUMAN, BARBARIAN, ERUDITE, WOOD_ELF, HIGH_ELF, DARK_ELF, HALF_ELF, DWARF, TROLL, OGRE, HALFLING, GNOME, IKSAR, VAHSHIR, FROGLOK, DRAKKIN); + const std::string msg_gender = StringFormat("gender: %u(M), %u(F)", MALE, FEMALE); + + if (helper_command_alias_fail(c, "bot_subcommand_bot_create", sep->arg[0], "botcreate")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [bot_name] [bot_class] [bot_race] [bot_gender]", sep->arg[0]); + c->Message(m_note, msg_class.c_str()); + c->Message(m_note, msg_race.c_str()); + c->Message(m_note, msg_gender.c_str()); + return; + } + + if (sep->arg[1][0] == '\0' || sep->IsNumber(1)) { + c->Message(m_fail, "You must [name] your bot"); + return; + } + std::string bot_name = sep->arg[1]; + + if (sep->arg[2][0] == '\0' || !sep->IsNumber(2)) { + c->Message(m_fail, msg_class.c_str()); + return; + } + uint8 bot_class = atoi(sep->arg[2]); + + if (sep->arg[3][0] == '\0' || !sep->IsNumber(3)) { + c->Message(m_fail, msg_race.c_str()); + return; + } + uint16 bot_race = atoi(sep->arg[3]); + + if (sep->arg[4][0] == '\0') { + c->Message(m_fail, msg_gender.c_str()); + return; + } + uint8 bot_gender = atoi(sep->arg[4]); + + helper_bot_create(c, bot_name, bot_class, bot_race, bot_gender); +} + +void bot_subcommand_bot_delete(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_bot_delete", sep->arg[0], "botdelete")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s", sep->arg[0]); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(m_fail, "You must a bot that you own to use this command"); + return; + } + + std::string TempErrorMessage; + + my_bot->DeleteBot(&TempErrorMessage); + if (!TempErrorMessage.empty()) { + c->Message(m_unknown, "Failed to delete '%s' due to database error: %s", my_bot->GetCleanName(), TempErrorMessage.c_str()); + return; + } + + auto bid = my_bot->GetBotID(); + std::string bot_name = my_bot->GetCleanName(); + + my_bot->Camp(false); + + c->Message(m_action, "Successfully deleted bot '%s' (id: %i)", bot_name.c_str(), bid); +} + +void bot_subcommand_bot_details(Client *c, const Seperator *sep) +{ + // TODO: Trouble-shoot model update issue + + if (helper_command_alias_fail(c, "bot_subcommand_bot_details", sep->arg[0], "botdetails")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [value: 0-n] (Drakkin bots only)", sep->arg[0]); + c->Message(m_note, "note: Actual limit is filter-based"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(m_fail, "You must a bot that you own to use this command"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(m_fail, "A numeric [value] is required to use this command"); + return; + } + + uint32 uvalue = atoi(sep->arg[1]); + + auto fail_type = BCEnum::AFT_None; + if (my_bot->GetRace() != DRAKKIN) + fail_type = BCEnum::AFT_Race; + else if (!PlayerAppearance::IsValidDetail(my_bot->GetRace(), my_bot->GetGender(), uvalue)) + fail_type = BCEnum::AFT_Value; + else + my_bot->SetDrakkinDetails(uvalue); + + if (helper_bot_appearance_fail(c, my_bot, fail_type, "details")) + return; + + helper_bot_appearance_form_final(c, my_bot); +} + +void bot_subcommand_bot_dye_armor(Client *c, const Seperator *sep) +{ + // TODO: Trouble-shoot model update issue + + const std::string msg_matslot = StringFormat("mat_slot: %c(All), %i(Head), %i(Chest), %i(Arms), %i(Wrists), %i(Hands), %i(Legs), %i(Feet)", + '*', MaterialHead, MaterialChest, MaterialArms, MaterialWrist, MaterialHands, MaterialLegs, MaterialFeet); + + if (helper_command_alias_fail(c, "bot_subcommand_bot_dye_armor", sep->arg[0], "botdyearmor")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [mat_slot] [red: 0-255] [green: 0-255] [blue: 0-255] ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(m_note, msg_matslot.c_str()); + return; + } + const int ab_mask = ActionableBots::ABM_NoFilter; + + uint8 material_slot = _MaterialInvalid; + int16 slot_id = INVALID_INDEX; + + bool dye_all = (sep->arg[1][0] == '*'); + if (!dye_all) { + material_slot = atoi(sep->arg[1]); + slot_id = Inventory::CalcSlotFromMaterial(material_slot); + + if (!sep->IsNumber(1) || slot_id == INVALID_INDEX || material_slot > MaterialFeet) { + c->Message(m_fail, "Valid [mat_slot]s for this command are:"); + c->Message(m_fail, msg_matslot.c_str()); + return; + } + } + + uint32 red_value = atoi(sep->arg[2]); + if (!sep->IsNumber(2) || red_value > 255) { + c->Message(m_fail, "Valid [red] values for this command are [0-255]"); + return; + } + + uint32 green_value = atoi(sep->arg[3]); + if (!sep->IsNumber(3) || green_value > 255) { + c->Message(m_fail, "Valid [green] values for this command are [0-255]"); + return; + } + + uint32 blue_value = atoi(sep->arg[4]); + if (!sep->IsNumber(4) || blue_value > 255) { + c->Message(m_fail, "Valid [blue] values for this command are [0-255]"); + return; + } + + uint32 rgb_value = ((0xFF) | (red_value << 16) | (green_value << 8) | (blue_value)); // watch the leading '(0xFF) | ' + + std::list sbl; + auto ab_type = ActionableBots::PopulateSBL(c, sep->arg[5], sbl, ab_mask); + if (ab_type == ActionableBots::ABT_None) + return; + + for (auto bot_iter : sbl) { + if (!bot_iter) + continue; + + if (!bot_iter->DyeArmor(slot_id, rgb_value, dye_all, (ab_type != ActionableBots::ABT_All))) { + c->Message(m_fail, "Failed to change armor color for '%s' due to unknown cause", bot_iter->GetCleanName()); + return; + } + + //if (dye_all) + // helper_bot_appearance_form_update(bot_iter); + } + + if (ab_type == ActionableBots::ABT_All) { + bool action_success = false; + if (dye_all) + action_success = botdb.SetAllArmorColors(c->CharacterID(), rgb_value); + else + action_success = botdb.SetAllArmorColorBySlot(c->CharacterID(), slot_id, rgb_value); + + if (!action_success) + c->Message(m_unknown, "Failed to save dye armor changes for your bots due to unknown cause"); + } +} + +void bot_subcommand_bot_eyes(Client *c, const Seperator *sep) +{ + // TODO: Trouble-shoot model update issue + + // not sure if left/right bias is allowed in pc-type entities (something is keeping them from being different) + if (helper_command_alias_fail(c, "bot_subcommand_bot_eyes", sep->arg[0], "boteyes")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + //c->Message(m_usage, "usage: %s [value:0-n] ([option: left | right])", sep->arg[0]); + c->Message(m_usage, "usage: %s [value: 0-n]", sep->arg[0]); + c->Message(m_note, "note: Actual limit is filter-based"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(m_fail, "You must a bot that you own to use this command"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(m_fail, "A numeric [value] is required to use this command"); + return; + } + + uint8 uvalue = atoi(sep->arg[1]); + + //uint8 eye_bias = 0; + //std::string arg2 = sep->arg[2]; + //if (!arg2.compare("left")) + // eye_bias = 1; + //else if (!arg2.compare("right")) + // eye_bias = 2; + + auto fail_type = BCEnum::AFT_None; + if (!PlayerAppearance::IsValidEyeColor(my_bot->GetRace(), my_bot->GetGender(), uvalue)) { + fail_type = BCEnum::AFT_Value; + } + else { + //if (eye_bias == 1) { + // my_bot->SetEyeColor1(uvalue); + //} + //else if (eye_bias == 2) { + // my_bot->SetEyeColor2(uvalue); + //} + //else { + my_bot->SetEyeColor1(uvalue); + my_bot->SetEyeColor2(uvalue); + //} + } + + if (helper_bot_appearance_fail(c, my_bot, fail_type, "eyes")) + return; + + c->Message(m_action, "This feature will update the next time your bot is spawned"); + //helper_bot_appearance_form_final(c, my_bot); +} + +void bot_subcommand_bot_face(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_bot_face", sep->arg[0], "botface")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [value: 0-n]", sep->arg[0]); + c->Message(m_note, "note: Actual limit is filter-based"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(m_fail, "You must a bot that you own to use this command"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(m_fail, "A numeric [value] is required to use this command"); + return; + } + + uint8 uvalue = atoi(sep->arg[1]); + + auto fail_type = BCEnum::AFT_None; + if (!PlayerAppearance::IsValidFace(my_bot->GetRace(), my_bot->GetGender(), uvalue)) { + fail_type = BCEnum::AFT_Value; + } + else { + uint8 old_woad = 0; + if (my_bot->GetRace() == BARBARIAN) + old_woad = ((my_bot->GetLuclinFace() / 10) * 10); + my_bot->SetLuclinFace((old_woad + uvalue)); + } + + if (helper_bot_appearance_fail(c, my_bot, fail_type, "face")) + return; + + helper_bot_appearance_form_final(c, my_bot); +} + +void bot_subcommand_bot_follow_distance(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_bot_follow_distance", sep->arg[0], "botfollowdistance")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [set] [distance] ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(m_usage, "usage: %s [clear] ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + return; + } + const int ab_mask = ActionableBots::ABM_NoFilter; + + uint32 bfd = BOT_DEFAULT_FOLLOW_DISTANCE; + bool set_flag = false; + int ab_arg = 2; + + if (!strcasecmp(sep->arg[1], "set")) { + if (!sep->IsNumber(2)) { + c->Message(m_fail, "A numeric [distance] is required to use this command"); + return; + } + + bfd = atoi(sep->arg[2]); + set_flag = true; + ab_arg = 3; + } + else if (strcasecmp(sep->arg[1], "clear")) { + c->Message(m_fail, "This command requires a [set | clear] argument"); + return; + } + + std::list sbl; + auto ab_type = ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, sep->arg[(ab_arg + 1)]); + if (ab_type == ActionableBots::ABT_None) + return; + + int bot_count = 0; + for (auto bot_iter : sbl) { + if (!bot_iter) + continue; + + bot_iter->SetFollowDistance(bfd); + if (ab_type != ActionableBots::ABT_All && !botdb.SetFollowDistance(c->CharacterID(), bot_iter->GetBotID(), bfd)) { + c->Message(m_unknown, "DATABASE ERROR: Could not change follow distance for bot %s", bot_iter->GetCleanName()); + return; + } + + ++bot_count; + } + + if (ab_type == ActionableBots::ABT_All) { + if (!botdb.SetAllFollowDistances(c->CharacterID(), bfd)) { + c->Message(m_unknown, "Failed to save follow distance changes for your bots due to unknown cause"); + return; + } + + c->Message(m_action, "%s all of your bot follow distances", set_flag ? "Set" : "Cleared"); + } + else { + c->Message(m_action, "%s %i of your spawned bot follow distances", (set_flag ? "Set" : "Cleared"), bot_count); + } +} + +void bot_subcommand_bot_hair_color(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_bot_hair_color", sep->arg[0], "bothaircolor")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [value: 0-n]", sep->arg[0]); + c->Message(m_note, "note: Actual limit is filter-based"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(m_fail, "You must a bot that you own to use this command"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(m_fail, "A numeric [value] is required to use this command"); + return; + } + + uint8 uvalue = atoi(sep->arg[1]); + + auto fail_type = BCEnum::AFT_None; + if (!PlayerAppearance::IsValidHairColor(my_bot->GetRace(), my_bot->GetGender(), uvalue)) + fail_type = BCEnum::AFT_Value; + else + my_bot->SetHairColor(uvalue); + + if (helper_bot_appearance_fail(c, my_bot, fail_type, "hair color")) + return; + + helper_bot_appearance_form_final(c, my_bot); +} + +void bot_subcommand_bot_hairstyle(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_bot_hairstyle", sep->arg[0], "bothairstyle")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [value: 0-n]", sep->arg[0]); + c->Message(m_note, "note: Actual limit is filter-based"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(m_fail, "You must a bot that you own to use this command"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(m_fail, "A numeric [value] is required to use this command"); + return; + } + + uint8 uvalue = atoi(sep->arg[1]); + + auto fail_type = BCEnum::AFT_None; + if (!PlayerAppearance::IsValidHair(my_bot->GetRace(), my_bot->GetGender(), uvalue)) + fail_type = BCEnum::AFT_Value; + else + my_bot->SetHairStyle(uvalue); + + if (helper_bot_appearance_fail(c, my_bot, fail_type, "hair style")) + return; + + helper_bot_appearance_form_final(c, my_bot); +} + +void bot_subcommand_bot_heritage(Client *c, const Seperator *sep) +{ + // TODO: Trouble-shoot model update issue + + if (helper_command_alias_fail(c, "bot_subcommand_bot_heritage", sep->arg[0], "botheritage")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [value: 0-n] (Drakkin bots only)", sep->arg[0]); + c->Message(m_note, "note: Actual limit is filter-based"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(m_fail, "You must a bot that you own to use this command"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(m_fail, "A numeric [value] is required to use this command"); + return; + } + + uint32 uvalue = atoi(sep->arg[1]); + + auto fail_type = BCEnum::AFT_None; + if (my_bot->GetRace() != DRAKKIN) + fail_type = BCEnum::AFT_Race; + else if (!PlayerAppearance::IsValidHeritage(my_bot->GetRace(), my_bot->GetGender(), uvalue)) + fail_type = BCEnum::AFT_Value; + else + my_bot->SetDrakkinHeritage(uvalue); + + if (helper_bot_appearance_fail(c, my_bot, fail_type, "heritage")) + return; + + helper_bot_appearance_form_final(c, my_bot); +} + +void bot_subcommand_bot_inspect_message(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_bot_inspect_message", sep->arg[0], "botinspectmessage")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [set | clear] ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(m_note, "Notes:"); + if (c->GetClientVersion() >= ClientVersion::SoF) { + c->Message(m_message, "- Self-inspect and type your bot's inspect message"); + c->Message(m_message, "- Close the self-inspect window to update the server"); + c->Message(m_message, "- Type '%s set' to set the bot's inspect message", sep->arg[0]); + } + else { + c->Message(m_message, "- Self-inspect and type your bot's inspect message"); + c->Message(m_message, "- Close the self-inspect window"); + c->Message(m_message, "- Self-inspect again to update the server"); + c->Message(m_message, "- Type '%s set' to set the bot's inspect message", sep->arg[0]); + } + return; + } + const int ab_mask = ActionableBots::ABM_NoFilter; + + bool set_flag = false; + if (!strcasecmp(sep->arg[1], "set")) { + set_flag = true; + } + else if (strcasecmp(sep->arg[1], "clear")) { + c->Message(15, "This command requires a [set | clear] argument"); + return; + } + + std::list sbl; + auto ab_type = ActionableBots::PopulateSBL(c, sep->arg[2], sbl, ab_mask, sep->arg[3]); + if (ab_type == ActionableBots::ABT_None) + return; + + const auto cms = &c->GetInspectMessage(); + + int bot_count = 0; + for (auto bot_iter : sbl) { + if (!bot_iter) + continue; + + auto bms = &bot_iter->GetInspectMessage(); + memset(bms, 0, sizeof(InspectMessage_Struct)); + if (set_flag) + memcpy(bms, cms, sizeof(InspectMessage_Struct)); + + if (ab_type != ActionableBots::ABT_All) + botdb.SetInspectMessage(bot_iter->GetBotID(), bms); + + ++bot_count; + } + + if (ab_type == ActionableBots::ABT_All) { + InspectMessage_Struct bms; + memset(&bms, 0, sizeof(InspectMessage_Struct)); + if (set_flag) + memcpy(&bms, cms, sizeof(InspectMessage_Struct)); + + if (!botdb.SetAllInspectMessages(c->CharacterID(), &bms)) { + c->Message(m_fail, "Failed to save inspect message changes for your bots due to unknown cause"); + return; + } + + c->Message(m_action, "%s all of your bot inspect messages", set_flag ? "Set" : "Cleared"); + } + else { + c->Message(m_action, "%s %i of your spawned bot inspect messages", set_flag ? "Set" : "Cleared", bot_count); + } +} + +void bot_subcommand_bot_list(Client *c, const Seperator *sep) +{ + // TODO: Consider re-working to use player race values instead of actual race + + enum { FilterClass, FilterRace, FilterName, FilterCount, MaskClass = (1 << FilterClass), MaskRace = (1 << FilterRace), MaskName = (1 << FilterName) }; + + if (helper_command_alias_fail(c, "bot_subcommand_bot_list", sep->arg[0], "botlist")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s ([class] [value]) ([race] [value]) ([name] [partial-full])", sep->arg[0]); + c->Message(m_note, "Note: filter criteria is orderless and optional"); + return; + } + + uint32 filter_value[FilterCount]; + int name_criteria_arg = 0; + memset(&filter_value, 0, sizeof(uint32) * FilterCount); + + int filter_mask = 0; + for (int i = 1; i < (FilterCount * 2); i += 2) { + if (sep->arg[i][0] == '\0') + break; + + if (!strcasecmp(sep->arg[i], "class")) { + filter_mask |= MaskClass; + filter_value[FilterClass] = atoi(sep->arg[i + 1]); + continue; + } + if (!strcasecmp(sep->arg[i], "race")) { + filter_mask |= MaskRace; + filter_value[FilterRace] = atoi(sep->arg[i + 1]); + continue; + } + if (!strcasecmp(sep->arg[i], "name")) { + filter_mask |= MaskName; + name_criteria_arg = (i + 1); + continue; + } + + c->Message(m_fail, "A numeric value or name is required to use the filter property of this command (f: '%s', v: '%s')", sep->arg[i], sep->arg[i + 1]); + return; + } + + std::string TempErrorMessage; + auto dbbl = Bot::GetBotList(c->CharacterID(), &TempErrorMessage); + + if (!TempErrorMessage.empty()) { + c->Message(m_fail, "Failed to load 'BotsAvailableList' due to unknown cause"); + return; + } + if (dbbl.empty()) { + c->Message(m_fail, "You have no bots"); + return; + } + + int bot_count = 0; + for (auto dbbl_iter : dbbl) { + if (filter_mask) { + if ((filter_mask & MaskClass) && filter_value[FilterClass] != dbbl_iter.BotClass) + continue; + if ((filter_mask & MaskRace) && filter_value[FilterRace] != dbbl_iter.BotRace) + continue; + if (filter_mask & MaskName) { + std::string name_criteria = sep->arg[name_criteria_arg]; + std::transform(name_criteria.begin(), name_criteria.end(), name_criteria.begin(), ::tolower); + std::string name_check = dbbl_iter.BotName; + std::transform(name_check.begin(), name_check.end(), name_check.begin(), ::tolower); + if (name_check.find(name_criteria) == std::string::npos) + continue; + } + } + + c->Message(m_message, "%s is a level %u %s %s %s", + dbbl_iter.BotName, + dbbl_iter.BotLevel, + Bot::RaceIdToString(dbbl_iter.BotRace).c_str(), + ((dbbl_iter.BotGender == FEMALE) ? ("Female") : ((dbbl_iter.BotGender == MALE) ? ("Male") : ("Neuter"))), + Bot::ClassIdToString(dbbl_iter.BotClass).c_str() + ); + + ++bot_count; + } + if (!bot_count) { + c->Message(m_fail, "You have no bots meeting this criteria"); + } + else { + c->Message(m_action, "%i of %i bot%s shown", bot_count, dbbl.size(), ((bot_count != 1) ? ("s") : (""))); + c->Message(m_message, "Your limit is %i bot%s", RuleI(Bots, CreationLimit), ((RuleI(Bots, CreationLimit) != 1) ? ("s") : (""))); + } +} + +void bot_subcommand_bot_out_of_combat(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_bot_out_of_combat", sep->arg[0], "botoutofcombat")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s ([option: on | off]) ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); + return; + } + const int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); + + std::string arg1 = sep->arg[1]; + + bool behavior_state = false; + bool toggle_behavior = true; + int ab_arg = 1; + if (!arg1.compare("on")) { + behavior_state = true; + toggle_behavior = false; + ab_arg = 2; + } + else if (!arg1.compare("off")) { + toggle_behavior = false; + ab_arg = 2; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, sep->arg[(ab_arg + 1)]) == ActionableBots::ABT_None) + return; + + for (auto bot_iter : sbl) { + if (!bot_iter) + continue; + + if (toggle_behavior) + bot_iter->SetAltOutOfCombatBehavior(!bot_iter->GetAltOutOfCombatBehavior()); + else + bot_iter->SetAltOutOfCombatBehavior(behavior_state); + + helper_bot_out_of_combat(c, bot_iter); + } +} + +void bot_subcommand_bot_report(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_bot_report", sep->arg[0], "botreport")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + return; + } + const int ab_mask = ActionableBots::ABM_NoFilter; + + std::string ab_type_arg = sep->arg[1]; + if (ab_type_arg.empty()) { + if (c->GetTarget()) { + if (c->GetTarget()->IsClient() && c->GetTarget()->CastToClient() == c) + ab_type_arg = "ownergroup"; + else if (c->GetTarget()->IsClient() && c->GetTarget()->CastToClient() != c) + ab_type_arg = "targetgroup"; + } + else { + ab_type_arg = "spawned"; + } + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, ab_type_arg.c_str(), sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) + return; + + for (auto bot_iter : sbl) { + if (!bot_iter) + continue; + + std::string report_msg = StringFormat("%s %s reports", Bot::ClassIdToString(bot_iter->GetClass()).c_str(), bot_iter->GetCleanName()); + report_msg.append(StringFormat(": %3.1f%% health", bot_iter->GetHPRatio())); + if (!IsNonSpellFighterClass(bot_iter->GetClass())) + report_msg.append(StringFormat(": %3.1f%% mana", bot_iter->GetManaRatio())); + + c->Message(m_message, "%s", report_msg.c_str()); + } +} + +void bot_subcommand_bot_spawn(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_bot_spawn", sep->arg[0], "botspawn")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [bot_name]", sep->arg[0]); + return; + } + + int rule_level = RuleI(Bots, BotCharacterLevel); + if (c->GetLevel() < rule_level) { + c->Message(m_fail, "You must be level %i to use bots", rule_level); + return; + } + + if (c->GetFeigned()) { + c->Message(m_fail, "You can not spawn a bot while feigned"); + return; + } + + std::string TempErrorMessage; + + int sbc = Bot::SpawnedBotCount(c->CharacterID(), &TempErrorMessage); + if (!TempErrorMessage.empty()) { + c->Message(m_unknown, "Database Error: %s", TempErrorMessage.c_str()); + return; + } + + int rule_limit = RuleI(Bots, SpawnLimit); + if (sbc >= rule_limit && !c->GetGM()) { + c->Message(m_fail, "You can not have more than %i spawned bots", rule_limit); + return; + } + + if (RuleB(Bots, QuestableSpawnLimit) && !c->GetGM()) { + int abc = Bot::AllowedBotSpawns(c->CharacterID(), &TempErrorMessage); + if (!TempErrorMessage.empty()) { + c->Message(m_unknown, "Database Error: %s", TempErrorMessage.c_str()); + return; + } + if (!abc) { + c->Message(m_fail, "You are not currently allowed any spawned bots"); + return; + } + if (sbc >= abc) { + c->Message(m_fail, "You have reached your current limit of %i spawned bots", abc); + return; + } + } + + if (sep->arg[1][0] == '\0' || sep->IsNumber(1)) { + c->Message(m_fail, "You must specify a [name] to use this command"); + return; + } + std::string bot_name = sep->arg[1]; + + auto bot_id = Bot::GetBotIDByBotName(bot_name); + if (Bot::GetBotOwnerCharacterID(bot_id, &TempErrorMessage) != c->CharacterID()) { + if (!TempErrorMessage.empty()) + c->Message(m_unknown, "Database Error: %s", TempErrorMessage.c_str()); + c->Message(m_fail, "You don't own a bot named '%s'", bot_name.c_str()); + return; + } + + if (entity_list.GetMobByBotID(bot_id)) { + c->Message(m_fail, "'%s' is already spawned in zone", bot_name.c_str()); + return; + } + + // this probably needs work... + if (c->GetGroup()) { + std::list group_list; + c->GetGroup()->GetMemberList(group_list); + for (auto member_iter : group_list) { + if (!member_iter) + continue; + if (member_iter->qglobal) // what is this?? really should have had a message to describe failure... (can't spawn bots if you are assigned to a task/instance?) + return; + if (!member_iter->qglobal && (member_iter->GetAppearance() != eaDead) && (member_iter->IsEngaged() || (member_iter->IsClient() && member_iter->CastToClient()->GetAggroCount()))) { + c->Message(m_fail, "You can't summon bots while you are engaged."); + return; + } + } + } + else if (c->GetAggroCount() > 0) { + c->Message(m_fail, "You can't spawn bots while you are engaged."); + return; + } + + //if (c->IsEngaged()) { + // c->Message(m_fail, "You can't spawn bots while you are engaged."); + // return; + //} + + auto my_bot = Bot::LoadBot(bot_id, &TempErrorMessage); + if (!TempErrorMessage.empty()) { + c->Message(m_unknown, "Database Error: %s", TempErrorMessage.c_str()); + safe_delete(my_bot); + return; + } + if (!my_bot) { + c->Message(m_fail, "No valid bot '%s' (id: %i) exists", bot_name.c_str(), bot_id); + return; + } + + my_bot->Spawn(c, &TempErrorMessage); // 'TempErrorMessage' not used... + + static const char* bot_spawn_message[16] = { + "A solid weapon is my ally!", // WARRIOR / 'generic' + "The pious shall never die!", // CLERIC + "I am the symbol of Light!", // PALADIN + "There are enemies near!", // RANGER + "Out of the shadows, I step!", // SHADOWKNIGHT + "Nature's fury shall be wrought!", // DRUID + "Your punishment will be my fist!", // MONK + "Music is the overture of battle! ", // BARD + "Daggers into the backs of my enemies!", // ROGUE + "More bones to grind!", // SHAMAN + "Death is only the beginning!", // NECROMANCER + "I am the harbinger of demise!", // WIZARD + "The elements are at my command!", // MAGICIAN + "No being can resist my charm!", // ENCHANTER + "Battles are won by hand and paw!", // BEASTLORD + "My bloodthirst shall not be quenched!" // BERSERKER + }; + + Bot::BotGroupSay(my_bot, "%s", bot_spawn_message[CLASSIDTOINDEX(my_bot->GetClass())]); +} + +void bot_subcommand_bot_stance(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_bot_stance", sep->arg[0], "botstance")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [current | value: 0-6] ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); + c->Message(m_note, "value: %u(%s), %u(%s), %u(%s), %u(%s), %u(%s), %u(%s), %u(%s)", + BotStancePassive, GetBotStanceName(BotStancePassive), + BotStanceBalanced, GetBotStanceName(BotStanceBalanced), + BotStanceEfficient, GetBotStanceName(BotStanceEfficient), + BotStanceReactive, GetBotStanceName(BotStanceReactive), + BotStanceAggressive, GetBotStanceName(BotStanceAggressive), + BotStanceBurn, GetBotStanceName(BotStanceBurn), + BotStanceBurnAE, GetBotStanceName(BotStanceBurnAE) + ); + return; + } + int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); + + bool current_flag = false; + auto bst = BotStanceUnknown; + + if (!strcasecmp(sep->arg[1], "current")) + current_flag = true; + else if (sep->IsNumber(1)) + bst = VALIDBOTSTANCE(atoi(sep->arg[1])); + + if (!current_flag && bst == BotStanceUnknown) { + c->Message(m_fail, "A [current] argument or valid numeric [value] is required to use this command"); + return; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[2], sbl, ab_mask, sep->arg[3]) == ActionableBots::ABT_None) + return; + + for (auto bot_iter : sbl) { + if (!bot_iter) + continue; + + if (!current_flag) { + bot_iter->SetBotStance(bst); + bot_iter->CalcChanceToCast(); + bot_iter->Save(); + } + + Bot::BotGroupSay(bot_iter, "My current stance is '%s' (%u)", GetBotStanceName(bot_iter->GetBotStance()), bot_iter->GetBotStance()); + } +} + +void bot_subcommand_bot_summon(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_bot_summon", sep->arg[0], "botsummon")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + return; + } + const int ab_mask = ActionableBots::ABM_NoFilter; + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) + return; + + for (auto bot_iter : sbl) { + if (!bot_iter) + continue; + + Bot::BotGroupSay(bot_iter, "Whee!"); + + bot_iter->WipeHateList(); + bot_iter->SetTarget(bot_iter->GetBotOwner()); + bot_iter->Warp(glm::vec3(c->GetPosition())); + + if (!bot_iter->HasPet()) + continue; + + bot_iter->GetPet()->WipeHateList(); + bot_iter->GetPet()->SetTarget(bot_iter); + bot_iter->GetPet()->Warp(glm::vec3(c->GetPosition())); + } + + if (sbl.size() == 1) + c->Message(m_action, "Summoned %s to you", ((sbl.front()) ? (sbl.front()->GetCleanName()) : ("'nullptr'"))); + else + c->Message(m_action, "Summoned %i bots to you", sbl.size()); +} + +void bot_subcommand_bot_tattoo(Client *c, const Seperator *sep) +{ + // TODO: Trouble-shoot model update issue + + if (helper_command_alias_fail(c, "bot_subcommand_bot_tattoo", sep->arg[0], "bottattoo")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [value: 0-n] (Drakkin bots only)", sep->arg[0]); + c->Message(m_note, "note: Actual limit is filter-based"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(m_fail, "You must a bot that you own to use this command"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(m_fail, "A numeric [value] is required to use this command"); + return; + } + + uint32 uvalue = atoi(sep->arg[1]); + + auto fail_type = BCEnum::AFT_None; + if (my_bot->GetRace() != DRAKKIN) + fail_type = BCEnum::AFT_Race; + else if (!PlayerAppearance::IsValidTattoo(my_bot->GetRace(), my_bot->GetGender(), uvalue)) + fail_type = BCEnum::AFT_Value; + else + my_bot->SetDrakkinTattoo(uvalue); + + if (helper_bot_appearance_fail(c, my_bot, fail_type, "tattoo")) + return; + + helper_bot_appearance_form_final(c, my_bot); +} + +void bot_subcommand_bot_toggle_archer(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_bot_toggle_archer", sep->arg[0], "bottogglearcher")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s ([option: on | off]) ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); + return; + } + const int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); + + std::string arg1 = sep->arg[1]; + + bool archer_state = false; + bool toggle_archer = true; + int ab_arg = 1; + if (!arg1.compare("on")) { + archer_state = true; + toggle_archer = false; + ab_arg = 2; + } + else if (!arg1.compare("off")) { + toggle_archer = false; + ab_arg = 2; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, sep->arg[(ab_arg + 1)]) == ActionableBots::ABT_None) + return; + + for (auto bot_iter : sbl) { + if (!bot_iter) + continue; + + if (toggle_archer) + bot_iter->SetBotArcher(!bot_iter->IsBotArcher()); + else + bot_iter->SetBotArcher(archer_state); + bot_iter->ChangeBotArcherWeapons(bot_iter->IsBotArcher()); + + if (bot_iter->GetClass() == RANGER && bot_iter->GetLevel() >= 61) + bot_iter->SetRangerAutoWeaponSelect(bot_iter->IsBotArcher()); + } +} + +void bot_subcommand_bot_toggle_helm(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_bot_toggle_helm", sep->arg[0], "bottogglehelm")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s ([option: on | off]) ([actionable: target | byname | ownergroup | botgroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + return; + } + const int ab_mask = ActionableBots::ABM_NoFilter; + + std::string arg1 = sep->arg[1]; + + bool helm_state = false; + bool toggle_helm = true; + int ab_arg = 1; + if (!arg1.compare("on")) { + helm_state = true; + toggle_helm = false; + ab_arg = 2; + } + else if (!arg1.compare("off")) { + toggle_helm = false; + ab_arg = 2; + } + + std::list sbl; + auto ab_type = ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, sep->arg[(ab_arg + 1)]); + if (ab_type == ActionableBots::ABT_None) + return; + + int bot_count = 0; + for (auto bot_iter : sbl) { + if (!bot_iter) + continue; + + if (toggle_helm) + bot_iter->SetShowHelm(!bot_iter->GetShowHelm()); + else + bot_iter->SetShowHelm(helm_state); + + if (ab_type != ActionableBots::ABT_All) { + if (!botdb.SetHelmAppearance(c->CharacterID(), bot_iter->GetBotID(), bot_iter->GetShowHelm())) { + c->Message(m_unknown, "DATABASE ERROR: Could not change helm appearance for bot %s", bot_iter->GetCleanName()); + return; + } + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); + SpawnAppearance_Struct* saptr = (SpawnAppearance_Struct*)outapp->pBuffer; + saptr->spawn_id = bot_iter->GetID(); + saptr->type = AT_ShowHelm; + saptr->parameter = bot_iter->GetShowHelm(); + + entity_list.QueueClients(bot_iter, outapp); + safe_delete(outapp); + + //helper_bot_appearance_form_update(bot_iter); + } + ++bot_count; + } + + if (ab_type == ActionableBots::ABT_All) { + bool action_success = false; + std::string query; + if (toggle_helm) + action_success = botdb.ToggleAllHelmAppearances(c->CharacterID()); + else + action_success = botdb.SetAllHelmAppearances(c->CharacterID(), helm_state); + if (!action_success) { + c->Message(m_unknown, "Failed to save helm changes for your bots due to unknown cause"); + return; + } + + c->Message(m_action, "%s all of your bot show helm flags", toggle_helm ? "Toggled" : (helm_state ? "Set" : "Cleared")); + } + else { + c->Message(m_action, "%s %i of your spawned bot show helm flags", toggle_helm ? "Toggled" : (helm_state ? "Set" : "Cleared"), bot_count); + } + + // Notes: + /* + [CLIENT OPCODE TEST] + [10-16-2015 :: 14:57:56] [Packet :: Client -> Server (Dump)] [OP_SpawnAppearance - 0x01d1] [Size: 10] + 0: A4 02 [2B 00] 01 00 00 00 - showhelm = true (client) + [10-16-2015 :: 14:57:56] [Packet :: Server -> Client (Dump)] [OP_SpawnAppearance - 0x01d1] [Size: 10] + 0: A4 02 [2B 00] 01 00 00 00 - showhelm = true (client) + + [10-16-2015 :: 14:58:02] [Packet :: Client -> Server (Dump)] [OP_SpawnAppearance - 0x01d1] [Size: 10] + 0: A4 02 [2B 00] 00 00 00 00 - showhelm = false (client) + [10-16-2015 :: 14:58:02] [Packet :: Server -> Client (Dump)] [OP_SpawnAppearance - 0x01d1] [Size: 10] + 0: A4 02 [2B 00] 00 00 00 00 - showhelm = false (client) + + [BOT OPCODE TEST] + [10-16-2015 :: 22:15:34] [Packet :: Client -> Server (Dump)] [OP_ChannelMessage - 0x0045] [Size: 167] + 0: 43 6C 65 72 69 63 62 6F - 74 00 00 00 00 00 00 00 | Clericbot....... + 16: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................ + 32: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................ + 48: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................ + 64: 43 6C 65 72 69 63 62 6F - 74 00 00 00 00 00 00 00 | Clericbot....... + 80: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................ + 96: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................ + 112: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................ + 128: 00 00 00 00 08 00 00 00 - CD CD CD CD CD CD CD CD | ................ + 144: 64 00 00 00 23 62 6F 74 - 20 73 68 6F 77 68 65 6C | d...#bot showhel + 160: 6D 20 6F 6E 00 | m on. + + [10-16-2015 :: 22:15:34] [Packet :: Server -> Client (Dump)] [OP_SpawnAppearance - 0x01d1] [Size: 10] + 0: A2 02 2B 00 01 00 00 00 - showhelm = true + + [10-16-2015 :: 22:15:40] [Packet :: Client -> Server (Dump)] [OP_ChannelMessage - 0x0045] [Size: 168] + 0: 43 6C 65 72 69 63 62 6F - 74 00 00 00 00 00 00 00 | Clericbot....... + 16: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................ + 32: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................ + 48: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................ + 64: 43 6C 65 72 69 63 62 6F - 74 00 00 00 00 00 00 00 | Clericbot....... + 80: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................ + 96: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................ + 112: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 | ................ + 128: 00 00 00 00 08 00 00 00 - CD CD CD CD CD CD CD CD | ................ + 144: 64 00 00 00 23 62 6F 74 - 20 73 68 6F 77 68 65 6C | d...#bot showhel + 160: 6D 20 6F 66 66 00 | m off. + + [10-16-2015 :: 22:15:40] [Packet :: Server -> Client (Dump)] [OP_SpawnAppearance - 0x01d1] [Size: 10] + 0: A2 02 2B 00 00 00 00 00 - showhelm = false + + *** Bot did not update using the OP_SpawnAppearance packet with AT_ShowHelm appearance type *** + */ +} + +void bot_subcommand_bot_update(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_bot_update", sep->arg[0], "botupdate")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s", sep->arg[0]); + return; + } + + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + if (sbl.empty()) { + c->Message(m_fail, "You currently have no spawned bots"); + return; + } + + int bot_count = 0; + for (auto bot_iter : sbl) { + if (!bot_iter || bot_iter->IsEngaged() || bot_iter->GetLevel() == c->GetLevel()) + continue; + + bot_iter->SetPetChooser(false); + bot_iter->CalcBotStats((sbl.size() == 1)); + ++bot_count; + } + + c->Message(m_action, "Updated %i of your %i spawned bots", bot_count, sbl.size()); +} + +void bot_subcommand_bot_woad(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_bot_woad", sep->arg[0], "botwoad")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [value: 0-n] (Barbarian bots only)", sep->arg[0]); + c->Message(m_note, "note: Actual limit is filter-based"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(m_fail, "You must a bot that you own to use this command"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(m_fail, "A numeric [value] is required to use this command"); + return; + } + + uint8 uvalue = atoi(sep->arg[1]); + + auto fail_type = BCEnum::AFT_None; + if (my_bot->GetRace() != BARBARIAN) { + fail_type = BCEnum::AFT_Race; + } + else if (!PlayerAppearance::IsValidWoad(my_bot->GetRace(), my_bot->GetGender(), uvalue)) { + fail_type = BCEnum::AFT_Value; + } + else { + uint8 old_face = (my_bot->GetLuclinFace() % 10); + my_bot->SetLuclinFace(((uvalue * 10) + old_face)); + } + + if (helper_bot_appearance_fail(c, my_bot, fail_type, "woad")) + return; + + helper_bot_appearance_form_final(c, my_bot); +} + +void bot_subcommand_botgroup_add_member(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_botgroup_add_member", sep->arg[0], "botgroupaddmember")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s [member_name] ([leader_name])", sep->arg[0]); + return; + } + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); + if (sbl.empty()) { + c->Message(m_fail, "You must [name] a new member as a bot that you own to use this command"); + return; + } + + auto new_member = sbl.front(); + if (!new_member) { + c->Message(m_unknown, "Error: New member bot dereferenced to nullptr"); + return; + } + + if (new_member->HasGroup()) { + c->Message(m_fail, "%s is already a current member of a group and can not join another one", new_member->GetCleanName()); + return; + } + + std::string error_message; + if (botdb.GetGroupIDByMemberID(new_member->GetBotID(), error_message)) { + if (!error_message.empty()) + c->Message(m_unknown, "Database Error: %s", error_message.c_str()); + c->Message(m_fail, "%s is already a current member of a bot-group", new_member->GetCleanName()); + return; + } + + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[2]); + if (sbl.empty()) + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a group leader as a bot that you own to use this command"); + return; + } + + auto group_leader = sbl.front(); + if (!group_leader) { + c->Message(m_unknown, "Error: Group leader bot dereferenced to nullptr"); + return; + } + + Group* group_inst = group_leader->GetGroup(); + if (!group_inst || group_inst->GetLeader() != group_leader) { + c->Message(m_fail, "%s is not the current leader of a group", group_leader->GetCleanName()); + return; + } + + if (!botdb.GetGroupIDByLeaderID(group_leader->GetBotID(), error_message)) { + if (!error_message.empty()) + c->Message(m_unknown, "Database Error: %s", error_message.c_str()); + c->Message(m_fail, "%s is not the current leader of a bot-group", group_leader->GetCleanName()); + return; + } + + if (!Bot::AddBotToGroup(new_member, group_inst)) { + c->Message(m_fail, "Could not add %s as a new member to %s's group", new_member->GetCleanName(), group_leader->GetCleanName()); + return; + } + + database.SetGroupID(new_member->GetName(), group_inst->GetID(), new_member->GetBotID()); + botdb.AddMemberToBotGroup(group_leader->GetBotID(), new_member->GetBotID(), error_message); + + std::string botgroup_name = botdb.GetGroupNameByLeaderID(group_leader->GetBotID(), error_message); + + c->Message(m_action, "Successfully added %s to bot-group %s", new_member->GetCleanName(), botgroup_name.c_str()); +} + +void bot_subcommand_botgroup_create(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_botgroup_create", sep->arg[0], "botgroupcreate")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s [group_name] ([leader_name])", sep->arg[0]); + return; + } + + std::string group_name_arg = sep->arg[1]; + if (group_name_arg.empty()) { + c->Message(m_fail, "You must specify a [name] for this bot-group to use this command"); + return; + } + if (botdb.DoesBotGroupExist(group_name_arg)) { + c->Message(m_fail, "The [name] %s already exists for a bot-group. Please choose another", group_name_arg.c_str()); + return; + } + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[2]); + if (sbl.empty()) + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a group leader as a bot that you own to use this command"); + return; + } + + auto group_leader = sbl.front(); + if (!group_leader) { + c->Message(m_unknown, "Error: Group leader bot dereferenced to nullptr"); + return; + } + + if (group_leader->HasGroup()) { + c->Message(m_fail, "%s is already a current member of a group", group_leader->GetCleanName()); + return; + } + + std::string error_message; + if (botdb.GetGroupIDByLeaderID(group_leader->GetBotID(), error_message)) { + if (!error_message.empty()) + c->Message(m_unknown, "Database Error: %s", error_message.c_str()); + c->Message(m_fail, "%s is already the current leader of a bot-group", group_leader->GetCleanName()); + return; + } + if (botdb.GetGroupIDByMemberID(group_leader->GetBotID(), error_message)) { + if (!error_message.empty()) + c->Message(m_unknown, "Database Error: %s", error_message.c_str()); + c->Message(m_fail, "%s is already a current member of a bot-group", group_leader->GetCleanName()); + return; + } + + Group* group_inst = new Group(group_leader); + if (!group_inst) { + c->Message(m_unknown, "Could not create a new group instance"); + return; + } + + if (!botdb.CreateBotGroup(group_name_arg, group_leader->GetBotID(), error_message)) { + if (!error_message.empty()) + c->Message(m_unknown, "Database Error: %s", error_message.c_str()); + c->Message(m_fail, "Could not create bot-group %s", group_name_arg.c_str()); + safe_delete(group_inst); + return; + } + entity_list.AddGroup(group_inst); + database.SetGroupID(group_leader->GetCleanName(), group_inst->GetID(), group_leader->GetBotID()); + database.SetGroupLeaderName(group_inst->GetID(), group_leader->GetCleanName()); + group_leader->SetFollowID(c->GetID()); + + c->Message(m_action, "Successfully created bot-group %s with %s as its leader", group_name_arg.c_str(), group_leader->GetCleanName()); +} + +void bot_subcommand_botgroup_delete(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_botgroup_delete", sep->arg[0], "botgroupdelete")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [botgroup_name]", sep->arg[0]); + return; + } + + std::string group_name_arg = sep->arg[1]; + if (group_name_arg.empty()) { + c->Message(m_fail, "You must specify a [name] for this bot-group to use this command"); + return; + } + + std::string error_message; + uint32 group_id = botdb.GetGroupIDForLoadGroup(c->CharacterID(), group_name_arg, error_message); + if (!group_id) { + if (!error_message.empty()) + c->Message(m_unknown, "Database Error: %s", error_message.c_str()); + c->Message(m_fail, "Could not locate group id for %s", group_name_arg.c_str()); + return; + } + + uint32 leader_id = botdb.GetLeaderIDByGroupID(group_id, error_message); + if (!leader_id) { + if (!error_message.empty()) + c->Message(m_unknown, "Database Error: %s", error_message.c_str()); + c->Message(m_fail, "Could not locate leader id for %s", group_name_arg.c_str()); + return; + } + + std::list gbl; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + std::map> groups_list = botdb.LoadGroup(group_name_arg, error_message); + + for (auto bot_iter : sbl) { + for (auto group_iter : groups_list[group_id]) { + if (bot_iter->GetBotID() == group_iter) { + gbl.push_back(bot_iter); + break; + } + } + } + gbl.unique(); + + for (auto group_member : gbl) { + if (group_member->HasGroup()) + Bot::RemoveBotFromGroup(group_member, group_member->GetGroup()); + } + + if (!botdb.DeleteBotGroup(leader_id, error_message)) { + if (!error_message.empty()) + c->Message(m_unknown, "Database Error: %s", error_message.c_str()); + c->Message(m_fail, "Failed to delete bot-group %s", group_name_arg.c_str()); + return; + } + + c->Message(m_action, "Successfully deleted bot-group %s", group_name_arg.c_str()); +} + +void bot_subcommand_botgroup_list(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_botgroup_list", sep->arg[0], "botgrouplist")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s", sep->arg[0]); + return; + } + + std::string error_message; + auto groups_list = botdb.GetGroupsListByOwnerID(c->CharacterID(), error_message); + if (groups_list.empty()) { + if (!error_message.empty()) + c->Message(m_unknown, "Database Error: %s", error_message.c_str()); + c->Message(m_fail, "You have no saved bot-groups"); + return; + } + + int botgroup_count = 0; + for (auto groups_iter : groups_list) + c->Message(m_message, "(%i) Bot-group name: %s | Leader: %s", (++botgroup_count), groups_iter.first.c_str(), groups_iter.second.c_str()); + + c->Message(m_action, "%i bot-groups listed", botgroup_count); +} + +void bot_subcommand_botgroup_load(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_botgroup_load", sep->arg[0], "botgroupload")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [botgroup_name]", sep->arg[0]); + return; + } + + std::string group_name_arg = sep->arg[1]; + if (group_name_arg.empty()) { + c->Message(m_fail, "You must specify the [name] of a bot-group to load to use this command"); + return; + } + if (!botdb.DoesBotGroupExist(group_name_arg)) { + c->Message(m_fail, "Bot-group %s does not exist", group_name_arg.c_str()); + return; + } + + Group* owner_group = c->GetGroup(); + if (owner_group) { + std::list member_list; + owner_group->GetClientList(member_list); + member_list.remove(nullptr); + + for (auto member_iter : member_list) { + if (member_iter->IsEngaged() || member_iter->GetAggroCount() > 0) { + c->Message(m_fail, "You can't spawn bots while your group is engaged"); + return; + } + } + } + else { + if (c->GetAggroCount() > 0) { + c->Message(m_fail, "You can't spawn bots while you are engaged"); + return; + } + } + + std::string error_message; + uint32 group_id = botdb.GetGroupIDForLoadGroup(c->CharacterID(), group_name_arg, error_message); + if (!group_id) { + if (!error_message.empty()) + c->Message(m_unknown, "Database Error: %s", error_message.c_str()); + c->Message(m_fail, "Can not resolve bot-group %s to a valid id", group_name_arg.c_str()); + return; + } + + std::map> group_list = botdb.LoadGroup(group_name_arg, error_message); + if (group_list.empty()) { + if (!error_message.empty()) + c->Message(m_unknown, "Database Error: %s", error_message.c_str()); + c->Message(m_fail, "Database returned an empty list for got-group %s", group_name_arg.c_str()); + return; + } + + int spawned_bot_count = Bot::SpawnedBotCount(c->CharacterID(), &error_message); + if (!error_message.empty()) { + c->Message(m_unknown, "Database Error: %s", error_message.c_str()); + return; + } + + if (RuleB(Bots, QuestableSpawnLimit)) { + const int allowed_bot_count = Bot::AllowedBotSpawns(c->CharacterID(), &error_message); + if (!error_message.empty()) { + c->Message(m_unknown, "Database Error: %s", error_message.c_str()); + return; + } + + if (!allowed_bot_count) { + c->Message(m_fail, "You can not spawn any bots"); + return; + } + + if (spawned_bot_count >= allowed_bot_count || (spawned_bot_count + group_list.begin()->second.size()) > allowed_bot_count) { + c->Message(m_fail, "You can not spawn more than %i bot%s (quest-limit)", allowed_bot_count, ((allowed_bot_count == 1) ? ("") : ("s"))); + return; + } + } + + const int allowed_bot_limit = RuleI(Bots, SpawnLimit); + if (spawned_bot_count >= allowed_bot_limit || (spawned_bot_count + group_list.begin()->second.size()) > allowed_bot_limit) { + c->Message(m_fail, "You can not spawn more than %i bot%s (hard-limit)", allowed_bot_limit, ((allowed_bot_limit == 1) ? ("") : ("s"))); + return; + } + + uint32 leader_id = botdb.GetLeaderIDByGroupName(group_name_arg, error_message); + if (!leader_id) { + if (!error_message.empty()) + c->Message(m_unknown, "Database Error: %s", error_message.c_str()); + c->Message(m_fail, "Can not locate bot-group leader id for %s", group_name_arg.c_str()); + return; + } + + auto group_leader = Bot::LoadBot(leader_id, &error_message); + if (!group_leader) { + if (!error_message.empty()) + c->Message(m_unknown, "Database Error: %s", error_message.c_str()); + c->Message(m_fail, "Could not spawn bot-group leader for %s", group_name_arg.c_str()); + safe_delete(group_leader); + return; + } + + group_leader->Spawn(c, &error_message); + // No error_message code in Bot::Spawn() + + Group* group_inst = new Group(group_leader); + + entity_list.AddGroup(group_inst); + database.SetGroupID(group_leader->GetCleanName(), group_inst->GetID(), group_leader->GetBotID()); + database.SetGroupLeaderName(group_inst->GetID(), group_leader->GetCleanName()); + group_leader->SetFollowID(c->GetID()); + + group_list[group_id].remove(0); + group_list[group_id].remove(group_leader->GetBotID()); + for (auto member_iter : group_list[group_id]) { + auto group_member = Bot::LoadBot(member_iter, &error_message); + if (!group_member) { + if (!error_message.empty()) + c->Message(m_unknown, "Database Error: %s", error_message.c_str()); + c->Message(m_fail, "Could not load bot id %i", member_iter); + safe_delete(group_member); + return; + } + + group_member->Spawn(c, &error_message); + // No error_message code in Bot::Spawn() + Bot::AddBotToGroup(group_member, group_inst); + } + + c->Message(m_action, "Successfully loaded bot-group %s", group_name_arg.c_str()); +} + +void bot_subcommand_botgroup_remove_member(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_botgroup_remove_member", sep->arg[0], "botgroupremovemember")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s ([member_name])", sep->arg[0]); + return; + } + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); + if (sbl.empty()) + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a group member as a bot that you own to use this command"); + return; + } + + auto group_member = sbl.front(); + if (!group_member) { + c->Message(m_unknown, "Error: Group member bot dereferenced to nullptr"); + return; + } + + if (!group_member->HasGroup()) { + c->Message(m_fail, "%s is not a current member of a group", group_member->GetCleanName()); + return; + } + + if (!Bot::RemoveBotFromGroup(group_member, group_member->GetGroup())) { + c->Message(m_fail, "Could not remove %s from their group", group_member->GetCleanName()); + return; + } + + std::string error_message; + if (!botdb.RemoveMemberFromBotGroup(group_member->GetBotID(), error_message)) { + if (!error_message.empty()) + c->Message(m_unknown, "Database Error: %s", error_message.c_str()); + c->Message(m_fail, "Could not remove %s from their bot-group", group_member->GetCleanName()); + return; + } + + c->Message(m_action, "Successfully removed %s from their bot-group", group_member->GetCleanName()); +} + +void bot_subcommand_circle(Client *c, const Seperator *sep) +{ + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Depart]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Depart) || helper_command_alias_fail(c, "bot_subcommand_circle", sep->arg[0], "circle")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [list | destination] ([option: single])", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Depart, DRUID); + return; + } + + bool single = false; + std::string single_arg = sep->arg[2]; + if (!single_arg.compare("single")) + single = true; + + std::string destination = sep->arg[1]; + if (!destination.compare("list")) { + auto my_druid_bot = ActionableBots::AsGroupMember_ByClass(c, c, DRUID); + helper_command_depart_list(c, my_druid_bot, nullptr, local_list, single); + return; + } + else if (destination.empty()) { + c->Message(m_fail, "A [destination] or [list] argument is required to use this command"); + return; + } + + ActionableTarget::Types actionable_targets; + Bot* my_bot = nullptr; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + bool cast_success = false; + for (auto list_iter : *local_list) { + auto local_entry = list_iter->SafeCastToDepart(); + if (helper_spell_check_fail(local_entry)) + continue; + if (local_entry->caster_class != DRUID) + continue; + if (local_entry->single != single) + continue; + if (destination.compare(spells[local_entry->spell_id].teleport_zone)) + continue; + + auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); + if (!target_mob) + continue; + + my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); + if (!my_bot) + continue; + + cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); + break; + } + + helper_no_available_bots(c, my_bot); +} + +void bot_subcommand_heal_rotation_adaptive_targeting(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_adaptive_targeting", sep->arg[0], "healrotationadaptivetargeting")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s ([member_name]) ([option: on | off])", sep->arg[0]); + return; + } + + std::string adaptive_targeting_arg; + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); + if (!sbl.empty()) { + adaptive_targeting_arg = sep->arg[2]; + } + else { + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + adaptive_targeting_arg = sep->arg[1]; + } + + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a current member as a bot that you own to use this command"); + return; + } + + auto current_member = sbl.front(); + if (!current_member) { + c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + bool hr_adaptive_targeting = false; + + if (!adaptive_targeting_arg.compare("on")) { + hr_adaptive_targeting = true; + } + else if (adaptive_targeting_arg.compare("off")) { + c->Message(m_action, "Adaptive targeting is currently '%s' for %s's Heal Rotation", (((*current_member->MemberOfHealRotation())->AdaptiveTargeting()) ? ("on") : ("off")), current_member->GetCleanName()); + return; + } + + (*current_member->MemberOfHealRotation())->SetAdaptiveTargeting(hr_adaptive_targeting); + + c->Message(m_action, "Adaptive targeting is now '%s' for %s's Heal Rotation", (((*current_member->MemberOfHealRotation())->AdaptiveTargeting()) ? ("on") : ("off")), current_member->GetCleanName()); +} + +void bot_subcommand_heal_rotation_add_member(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_add_member", sep->arg[0], "healrotationaddmember")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s [new_member_name] ([member_name])", sep->arg[0]); + return; + } + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); + if (sbl.empty()) { + c->Message(m_fail, "You must [name] a new member as a bot that you own to use this command"); + return; + } + + auto new_member = sbl.front(); + if (!new_member) { + c->Message(m_unknown, "Error: New member bot dereferenced to nullptr"); + return; + } + + if (new_member->IsHealRotationMember()) { + c->Message(m_fail, "%s is already a current member of a Heal Rotation and can not join another one", new_member->GetCleanName()); + return; + } + + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[2]); + if (sbl.empty()) + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a current member as a bot that you own to use this command"); + return; + } + + auto current_member = sbl.front(); + if (!current_member) { + c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + if (!new_member->JoinHealRotationMemberPool(current_member->MemberOfHealRotation())) { + c->Message(m_fail, "Failed to add %s as a current member of this Heal Rotation", new_member->GetCleanName()); + return; + } + + c->Message(m_action, "Successfully added %s as a current member of this Heal Rotation", new_member->GetCleanName()); +} + +void bot_subcommand_heal_rotation_add_target(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_add_target", sep->arg[0], "healrotationaddtarget")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s [heal_target_name] ([member_name])", sep->arg[0]); + return; + } + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[2]); + if (sbl.empty()) + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a current member as a bot that you own to use this command"); + return; + } + + auto current_member = sbl.front(); + if (!current_member) { + c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(m_fail, "%s is not a member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + auto heal_target = entity_list.GetMob(sep->arg[1]); + if (!heal_target) { + c->Message(m_fail, "No target exists by the name '%s'", sep->arg[1]); + return; + } + + if ((!heal_target->IsClient() && !heal_target->IsBot() && !heal_target->IsPet()) || + (heal_target->IsPet() && (!heal_target->GetOwner() || (!heal_target->GetOwner()->IsClient() && !heal_target->GetOwner()->IsBot())))) + { + c->Message(m_fail, "%s's entity type is not an allowable heal target", heal_target->GetCleanName()); + return; + } + + if (!heal_target->JoinHealRotationTargetPool(current_member->MemberOfHealRotation())) { + c->Message(m_fail, "Failed to add heal target with a name of '%s'", heal_target->GetCleanName()); + return; + } + + c->Message(m_action, "Successfully added heal target %s to %s's Heal Rotation", heal_target->GetCleanName(), current_member->GetCleanName()); +} + +void bot_subcommand_heal_rotation_adjust_critical(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_adjust_critical", sep->arg[0], "healrotationadjustcritical")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s [armor_type] [value: %3.1f-%3.1f | + | -] ([member_name])", sep->arg[0], CRITICAL_HP_RATIO_BASE, SAFE_HP_RATIO_BASE); + c->Message(m_note, "armor_types: %u(Base), %u(Cloth), %u(Leather), %u(Chain), %u(Plate)", + ARMOR_TYPE_UNKNOWN, ARMOR_TYPE_CLOTH, ARMOR_TYPE_LEATHER, ARMOR_TYPE_CHAIN, ARMOR_TYPE_PLATE); + return; + } + + std::string armor_type_arg = sep->arg[1]; + std::string critical_arg = sep->arg[2]; + + uint8 armor_type_value = 255; + if (sep->IsNumber(1)) + armor_type_value = atoi(armor_type_arg.c_str()); + + if (armor_type_value > ARMOR_TYPE_LAST) { + c->Message(m_fail, "You must specify a valid [armor_type: %u-%u] to use this command", ARMOR_TYPE_FIRST, ARMOR_TYPE_LAST); + return; + } + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[3]); + if (sbl.empty()) + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a current member as a bot that you own to use this command"); + return; + } + + auto current_member = sbl.front(); + if (!current_member) { + c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + float critical_ratio = CRITICAL_HP_RATIO_BASE; + if (sep->IsNumber(2)) + critical_ratio = atof(critical_arg.c_str()); + else if (!critical_arg.compare("+")) + critical_ratio = (*current_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(armor_type_value) + HP_RATIO_DELTA; + else if (!critical_arg.compare("-")) + critical_ratio = (*current_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(armor_type_value) - HP_RATIO_DELTA; + + if (critical_ratio > SAFE_HP_RATIO_BASE) + critical_ratio = SAFE_HP_RATIO_BASE; + if (critical_ratio < CRITICAL_HP_RATIO_BASE) + critical_ratio = CRITICAL_HP_RATIO_BASE; + + if (!(*current_member->MemberOfHealRotation())->SetArmorTypeCriticalHPRatio(armor_type_value, critical_ratio)) { + c->Message(m_fail, "Critical value %3.1f%%(%u) exceeds safe value %3.1f%%(%u) for %s's Heal Rotation", + critical_ratio, armor_type_value, (*current_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(armor_type_value), armor_type_value, current_member->GetCleanName()); + return; + } + + c->Message(m_action, "Class Armor Type %u critical value %3.1f%% set for %s's Heal Rotation", + armor_type_value, (*current_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(armor_type_value), current_member->GetCleanName()); +} + +void bot_subcommand_heal_rotation_adjust_safe(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_adjust_safe", sep->arg[0], "healrotationadjustsafe")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s [value: %3.1f-%3.1f | + | -] ([member_name])", sep->arg[0], CRITICAL_HP_RATIO_BASE, SAFE_HP_RATIO_BASE); + c->Message(m_note, "armor_types: %u(Base), %u(Cloth), %u(Leather), %u(Chain), %u(Plate)", + ARMOR_TYPE_UNKNOWN, ARMOR_TYPE_CLOTH, ARMOR_TYPE_LEATHER, ARMOR_TYPE_CHAIN, ARMOR_TYPE_PLATE); + return; + } + + std::string armor_type_arg = sep->arg[1]; + std::string safe_arg = sep->arg[2]; + + uint8 armor_type_value = 255; + if (sep->IsNumber(1)) + armor_type_value = atoi(armor_type_arg.c_str()); + + if (armor_type_value > ARMOR_TYPE_LAST) { + c->Message(m_fail, "You must specify a valid [armor_type: %u-%u] to use this command", ARMOR_TYPE_FIRST, ARMOR_TYPE_LAST); + return; + } + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[3]); + if (sbl.empty()) + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a current member as a bot that you own to use this command"); + return; + } + + auto current_member = sbl.front(); + if (!current_member) { + c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + float safe_ratio = SAFE_HP_RATIO_BASE; + if (sep->IsNumber(2)) + safe_ratio = atof(safe_arg.c_str()); + else if (!safe_arg.compare("+")) + safe_ratio = (*current_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(armor_type_value) + HP_RATIO_DELTA; + else if (!safe_arg.compare("-")) + safe_ratio = (*current_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(armor_type_value) - HP_RATIO_DELTA; + + if (safe_ratio > SAFE_HP_RATIO_BASE) + safe_ratio = SAFE_HP_RATIO_BASE; + if (safe_ratio < CRITICAL_HP_RATIO_BASE) + safe_ratio = CRITICAL_HP_RATIO_BASE; + + if (!(*current_member->MemberOfHealRotation())->SetArmorTypeSafeHPRatio(armor_type_value, safe_ratio)) { + c->Message(m_fail, "Safe value %3.1f%%(%u) does not exceed critical value %3.1f%%(%u) for %s's Heal Rotation", + safe_ratio, armor_type_value, (*current_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(armor_type_value), armor_type_value, current_member->GetCleanName()); + return; + } + + c->Message(m_action, "Class Armor Type %u safe value %3.1f%% set for %s's Heal Rotation", + armor_type_value, (*current_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(armor_type_value), current_member->GetCleanName()); +} + +void bot_subcommand_heal_rotation_casting_override(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_casting_override", sep->arg[0], "healrotationcastingoverride")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s ([member_name]) ([option: on | off])", sep->arg[0]); + return; + } + + std::string casting_override_arg; + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); + if (!sbl.empty()) { + casting_override_arg = sep->arg[2]; + } + else { + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + casting_override_arg = sep->arg[1]; + } + + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a current member as a bot that you own to use this command"); + return; + } + + auto current_member = sbl.front(); + if (!current_member) { + c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + bool hr_casting_override = false; + + if (!casting_override_arg.compare("on")) { + hr_casting_override = true; + } + else if (casting_override_arg.compare("off")) { + c->Message(m_action, "Fast heals are currently '%s' for %s's Heal Rotation", (((*current_member->MemberOfHealRotation())->CastingOverride()) ? ("on") : ("off")), current_member->GetCleanName()); + return; + } + + (*current_member->MemberOfHealRotation())->SetCastingOverride(hr_casting_override); + + c->Message(m_action, "Fast heals are now '%s' for %s's Heal Rotation", (((*current_member->MemberOfHealRotation())->CastingOverride()) ? ("on") : ("off")), current_member->GetCleanName()); +} + +void bot_subcommand_heal_rotation_change_interval(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_change_interval", sep->arg[0], "healrotationchangeinterval")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s ([member_name]) ([interval=%u: %u-%u(seconds)])", + sep->arg[0], CASTING_CYCLE_DEFAULT_INTERVAL_S, CASTING_CYCLE_MINIMUM_INTERVAL_S, CASTING_CYCLE_MAXIMUM_INTERVAL_S); + return; + } + + std::string change_interval_arg; + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); + if (!sbl.empty()) { + change_interval_arg = sep->arg[2]; + } + else { + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + change_interval_arg = sep->arg[1]; + } + + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a current member as a bot that you own to use this command"); + return; + } + + auto current_member = sbl.front(); + if (!current_member) { + c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + uint32 hr_change_interval_s = CASTING_CYCLE_DEFAULT_INTERVAL_S; + + if (!change_interval_arg.empty()) { + hr_change_interval_s = atoi(change_interval_arg.c_str()); + } + else { + hr_change_interval_s = (*current_member->MemberOfHealRotation())->IntervalS(); + c->Message(m_action, "Casting interval is currently '%i' second%s for %s's Heal Rotation", hr_change_interval_s, ((hr_change_interval_s == 1) ? ("") : ("s")), current_member->GetCleanName()); + return; + } + + if (hr_change_interval_s < CASTING_CYCLE_MINIMUM_INTERVAL_S || hr_change_interval_s > CASTING_CYCLE_MAXIMUM_INTERVAL_S) + hr_change_interval_s = CASTING_CYCLE_DEFAULT_INTERVAL_S; + + (*current_member->MemberOfHealRotation())->SetIntervalS(hr_change_interval_s); + + hr_change_interval_s = (*current_member->MemberOfHealRotation())->IntervalS(); + c->Message(m_action, "Casting interval is now '%i' second%s for %s's Heal Rotation", hr_change_interval_s, ((hr_change_interval_s == 1) ? ("") : ("s")), current_member->GetCleanName()); +} + +void bot_subcommand_heal_rotation_clear_targets(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_clear_targets", sep->arg[0], "healrotationcleartargets")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s ([member_name])", sep->arg[0]); + return; + } + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); + if (sbl.empty()) + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a current member as a bot that you own to use this command"); + return; + } + + auto current_member = sbl.front(); + if (!current_member) { + c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(m_fail, "%s is not a member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + if (!(*current_member->MemberOfHealRotation())->ClearTargetPool()) { + c->Message(m_fail, "Failed to clear all targets from %s's Heal Rotation", current_member->GetCleanName()); + return; + } + + c->Message(m_action, "All targets have been cleared from %s's Heal Rotation", current_member->GetCleanName()); +} + +void bot_subcommand_heal_rotation_create(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_create", sep->arg[0], "healrotationcreate")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s ([creator_name]) ([interval=%u: %u-%u(seconds)] [fastheals=off: on | off] [adaptivetargeting=off: on | off] [castingoverride=off: on | off])", + sep->arg[0], CASTING_CYCLE_DEFAULT_INTERVAL_S, CASTING_CYCLE_MINIMUM_INTERVAL_S, CASTING_CYCLE_MAXIMUM_INTERVAL_S); + return; + } + + std::string interval_arg; + std::string fast_heals_arg; + std::string adaptive_targeting_arg; + std::string casting_override_arg; + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); + if (!sbl.empty()) { + interval_arg = sep->arg[2]; + fast_heals_arg = sep->arg[3]; + adaptive_targeting_arg = sep->arg[4]; + casting_override_arg = sep->arg[5]; + } + else { + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + interval_arg = sep->arg[1]; + fast_heals_arg = sep->arg[2]; + adaptive_targeting_arg = sep->arg[3]; + casting_override_arg = sep->arg[4]; + } + + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a creator as a bot that you own to use this command"); + return; + } + + auto creator_member = sbl.front(); + if (!creator_member) { + c->Message(m_unknown, "Error: Creator bot dereferenced to nullptr"); + return; + } + + if (creator_member->IsHealRotationMember()) { + c->Message(m_fail, "%s is already a current member of a Heal Rotation", creator_member->GetCleanName()); + return; + } + + uint32 hr_interval_s = CASTING_CYCLE_DEFAULT_INTERVAL_S; + bool hr_fast_heals = false; + bool hr_adaptive_targeting = false; + bool hr_casting_override = false; + + if (!casting_override_arg.compare("on")) { + hr_casting_override = true; + if (!adaptive_targeting_arg.compare("on")) + hr_adaptive_targeting = true; + if (!fast_heals_arg.compare("on")) + hr_fast_heals = true; + hr_interval_s = atoi(interval_arg.c_str()); + } + else if (!casting_override_arg.compare("off")) { + if (!adaptive_targeting_arg.compare("on")) + hr_adaptive_targeting = true; + if (!fast_heals_arg.compare("on")) + hr_fast_heals = true; + hr_interval_s = atoi(interval_arg.c_str()); + } + + if (hr_interval_s < CASTING_CYCLE_MINIMUM_INTERVAL_S || hr_interval_s > CASTING_CYCLE_MAXIMUM_INTERVAL_S) + hr_interval_s = CASTING_CYCLE_DEFAULT_INTERVAL_S; + + hr_interval_s *= 1000; // convert to milliseconds for Bot/HealRotation constructor + + if (!creator_member->CreateHealRotation(hr_interval_s, hr_fast_heals, hr_adaptive_targeting, hr_casting_override)) { + c->Message(m_fail, "Failed to add %s as a current member to a new Heal Rotation", creator_member->GetCleanName()); + return; + } + + c->Message(m_action, "Successfully added %s as a current member to a new Heal Rotation", creator_member->GetCleanName()); +} + +void bot_subcommand_heal_rotation_fast_heals(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_fast_heals", sep->arg[0], "healrotationfastheals")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s ([member_name]) ([option: on | off])", sep->arg[0]); + return; + } + + std::string fast_heals_arg; + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); + if (!sbl.empty()) { + fast_heals_arg = sep->arg[2]; + } + else { + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + fast_heals_arg = sep->arg[1]; + } + + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a current member as a bot that you own to use this command"); + return; + } + + auto current_member = sbl.front(); + if (!current_member) { + c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + bool hr_fast_heals = false; + + if (!fast_heals_arg.compare("on")) { + hr_fast_heals = true; + } + else if (fast_heals_arg.compare("off")) { + c->Message(m_action, "Fast heals are currently '%s' for %s's Heal Rotation", (((*current_member->MemberOfHealRotation())->FastHeals()) ? ("on") : ("off")), current_member->GetCleanName()); + return; + } + + (*current_member->MemberOfHealRotation())->SetFastHeals(hr_fast_heals); + + c->Message(m_action, "Fast heals are now '%s' for %s's Heal Rotation", (((*current_member->MemberOfHealRotation())->FastHeals()) ? ("on") : ("off")), current_member->GetCleanName()); +} + +void bot_subcommand_heal_rotation_list(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_list", sep->arg[0], "healrotationlist")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s ([member_name])", sep->arg[0]); + return; + } + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); + if (sbl.empty()) + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a current member as a bot that you own to use this command"); + return; + } + + auto current_member = sbl.front(); + if (!current_member) { + c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + c->Message(m_note, "Heal Rotation Settings:"); + + c->Message(m_message, "Current state: %s", (((*current_member->MemberOfHealRotation())->IsActive()) ? ("active") : ("inactive"))); + c->Message(m_message, "Casting interval: %i seconds", (*current_member->MemberOfHealRotation())->IntervalS()); + c->Message(m_message, "Fast heals: '%s'", (((*current_member->MemberOfHealRotation())->FastHeals()) ? ("on") : ("off"))); + c->Message(m_message, "Adaptive targeting: '%s'", (((*current_member->MemberOfHealRotation())->AdaptiveTargeting()) ? ("on") : ("off"))); + c->Message(m_message, "Casting override: '%s'", (((*current_member->MemberOfHealRotation())->CastingOverride()) ? ("on") : ("off"))); + + c->Message(m_message, "Base hp limits - critical: %3.1f%%, safe: %3.1f%%", + (*current_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(ARMOR_TYPE_UNKNOWN), + (*current_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(ARMOR_TYPE_UNKNOWN)); + c->Message(m_message, "Cloth hp limits - critical: %3.1f%%, safe: %3.1f%%", + (*current_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(ARMOR_TYPE_CLOTH), + (*current_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(ARMOR_TYPE_CLOTH)); + c->Message(m_message, "Leather hp limits - critical: %3.1f%%, safe: %3.1f%%", + (*current_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(ARMOR_TYPE_LEATHER), + (*current_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(ARMOR_TYPE_LEATHER)); + c->Message(m_message, "Chain hp limits - critical: %3.1f%%, safe: %3.1f%%", + (*current_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(ARMOR_TYPE_CHAIN), + (*current_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(ARMOR_TYPE_CHAIN)); + c->Message(m_message, "Plate hp limits - critical: %3.1f%%, safe: %3.1f%%", + (*current_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(ARMOR_TYPE_PLATE), + (*current_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(ARMOR_TYPE_PLATE)); + + c->Message(m_note, "Heal Rotation Members:"); + + int member_index = 0; + auto member_pool = (*current_member->MemberOfHealRotation())->MemberList(); + for (auto member_iter : *member_pool) { + if (!member_iter) + continue; + + c->Message(m_message, "(%i) %s", (++member_index), member_iter->GetCleanName()); + } + if (!member_index) + c->Message(m_fail, "(0) None"); + + c->Message(m_note, "Heal Rotation Targets:"); + + int target_index = 0; + auto target_pool = (*current_member->MemberOfHealRotation())->TargetList(); + for (auto target_iter : *target_pool) { + if (!target_iter) + continue; + + c->Message(m_message, "(%i) %s", (++target_index), target_iter->GetCleanName()); + } + if (!target_index) + c->Message(m_message, "(0) None"); +} + +void bot_subcommand_heal_rotation_remove_member(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_remove_member", sep->arg[0], "healrotationremovemember")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s ([member_name])", sep->arg[0]); + return; + } + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); + if (sbl.empty()) + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a current member as a bot that you own to use this command"); + return; + } + + auto current_member = sbl.front(); + if (!current_member) { + c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + if (!current_member->LeaveHealRotationMemberPool()) { + c->Message(m_fail, "Failed to remove %s from their Heal Rotation", current_member->GetCleanName()); + return; + } + + c->Message(m_action, "%s has been removed from their Heal Rotation", current_member->GetCleanName()); +} + +void bot_subcommand_heal_rotation_remove_target(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_remove_target", sep->arg[0], "healrotationremovetarget")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s [heal_target_name] ([member_name])", sep->arg[0]); + return; + } + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[2]); + if (sbl.empty()) + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a current member as a bot that you own to use this command"); + return; + } + + auto current_member = sbl.front(); + if (!current_member) { + c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + auto heal_target = entity_list.GetMob(sep->arg[1]); + if (!heal_target) { + c->Message(m_fail, "No target exists by the name '%s'", sep->arg[1]); + return; + } + + if (!current_member->MemberOfHealRotation()->get()->IsTargetInPool(heal_target) || !heal_target->LeaveHealRotationTargetPool()) { + c->Message(m_fail, "Failed to remove heal target with a name of '%s'", heal_target->GetCleanName()); + return; + } + + c->Message(m_action, "Successfully removed heal target %s from %s's Heal Rotation", heal_target->GetCleanName(), current_member->GetCleanName()); +} + +void bot_subcommand_heal_rotation_reset_limits(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_reset_limits", sep->arg[0], "healrotationresetlimits")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s ([member_name])", sep->arg[0]); + return; + } + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); + if (sbl.empty()) + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a current member as a bot that you own to use this command"); + return; + } + + auto current_member = sbl.front(); + if (!current_member) { + c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + (*current_member->MemberOfHealRotation())->ResetArmorTypeHPLimits(); + + c->Message(m_action, "Class Armor Type HP limit criteria has been set to default values for %s's Heal Rotation", current_member->GetCleanName()); +} + +void bot_subcommand_heal_rotation_start(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_start", sep->arg[0], "healrotationstart")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s ([member_name])", sep->arg[0]); + return; + } + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); + if (sbl.empty()) + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a current member as a bot that you own to use this command"); + return; + } + + auto current_member = sbl.front(); + if (!current_member) { + c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + if ((*current_member->MemberOfHealRotation())->IsActive()) { + c->Message(m_fail, "%s's Heal Rotation is already active", current_member->GetCleanName()); + return; + } + + if (!current_member->MemberOfHealRotation()->get()->Start()) { + c->Message(m_fail, "Failed to start %s's Heal Rotation", current_member->GetCleanName()); + return; + } + + c->Message(m_action, "%s's Heal Rotation is now active", current_member->GetCleanName()); +} + +void bot_subcommand_heal_rotation_stop(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_stop", sep->arg[0], "healrotationstop")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s ([member_name])", sep->arg[0]); + return; + } + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); + if (sbl.empty()) + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a current member as a bot that you own to use this command"); + return; + } + + auto current_member = sbl.front(); + if (!current_member) { + c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + if (!(*current_member->MemberOfHealRotation())->IsActive()) { + c->Message(m_fail, "%s's Heal Rotation is already inactive", current_member->GetCleanName()); + return; + } + + if (!current_member->MemberOfHealRotation()->get()->Stop()) { + c->Message(m_fail, "Failed to stop %s's Heal Rotation", current_member->GetCleanName()); + return; + } + + c->Message(m_action, "%s's Heal Rotation is now inactive", current_member->GetCleanName()); +} + +void bot_subcommand_inventory_give(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_inventory_give", sep->arg[0], "inventorygive")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); + return; + } + int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) + return; + + auto my_bot = sbl.front(); + if (!my_bot) { + c->Message(m_unknown, "ActionableBots returned 'nullptr'"); + return; + } + + my_bot->FinishTrade(c, Bot::BotTradeClientNoDropNoTrade); +} + +void bot_subcommand_inventory_list(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_inventory_list", sep->arg[0], "inventorylist")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); + return; + } + int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) + return; + + auto my_bot = sbl.front(); + if (!my_bot) { + c->Message(m_unknown, "ActionableBots returned 'nullptr'"); + return; + } + + std::string TempErrorMessage; + my_bot->GetBotItemsCount(&TempErrorMessage); // database check to avoid false 'vacancy' reporting? + if (!TempErrorMessage.empty()) { + c->Message(m_unknown, "Database Error: %s", TempErrorMessage.c_str()); + return; + } + + const ItemInst* inst = nullptr; + const Item_Struct* item = nullptr; + bool is2Hweapon = false; + + std::string item_link; + Client::TextLink linker; + linker.SetLinkType(linker.linkItemInst); + + for (int i = EmuConstants::EQUIPMENT_BEGIN; i <= (EmuConstants::EQUIPMENT_END + 1); ++i) { + if ((i == MainSecondary) && is2Hweapon) + continue; + + inst = my_bot->CastToBot()->GetBotItem(i == 22 ? 9999 : i); + if (!inst || !inst->GetItem()) { + c->Message(m_message, "I need something for my %s (slot %i)", GetBotEquipSlotName(i), (i == 22 ? 9999 : i)); + continue; + } + + item = inst->GetItem(); + if ((i == MainPrimary) && ((item->ItemType == ItemType2HSlash) || (item->ItemType == ItemType2HBlunt) || (item->ItemType == ItemType2HPiercing))) { + is2Hweapon = true; + } + + linker.SetItemInst(inst); + item_link = linker.GenerateLink(); + c->Message(m_message, "Using %s in my %s (slot %i)", item_link.c_str(), GetBotEquipSlotName(i), (i == 22 ? 9999 : i)); + } +} + +void bot_subcommand_inventory_remove(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_inventory_remove", sep->arg[0], "inventoryremove")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [slotid: 0-22] ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); + return; + } + int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); + + if (c->GetTradeskillObject() || (c->trade->state == Trading)) { + c->Message_StringID(MT_Tell, MERCHANT_BUSY); + return; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[2], sbl, ab_mask, sep->arg[3]) == ActionableBots::ABT_None) + return; + + auto my_bot = sbl.front(); + if (!my_bot) { + c->Message(m_unknown, "ActionableBots returned 'nullptr'"); + return; + } + + std::string TempErrorMessage; + my_bot->GetBotItemsCount(&TempErrorMessage); // added same check as in bot_subcommand_inventory_list() - same note + if (!TempErrorMessage.empty()) { + c->Message(m_unknown, "Database Error: %s", TempErrorMessage.c_str()); + return; + } + + int slotId = atoi(sep->arg[1]); + if (!sep->IsNumber(1) || ((slotId > EmuConstants::EQUIPMENT_END || slotId < EmuConstants::EQUIPMENT_BEGIN) && slotId != 9999)) { + c->Message(m_fail, "Valid slots are 0-21 or 9999"); + return; + } + + const Item_Struct* itm = nullptr; + const ItemInst* itminst = my_bot->GetBotItem(slotId); + if (itminst) + itm = itminst->GetItem(); + + if (itminst && itm && c->CheckLoreConflict(itm)) { + c->Message_StringID(0, PICK_LORE); + return; + } + + for (int m = AUG_BEGIN; m < EmuConstants::ITEM_COMMON_SIZE; ++m) { + if (!itminst) + break; + + ItemInst *itma = itminst->GetAugment(m); + if (!itma) + continue; + if (!c->CheckLoreConflict(itma->GetItem())) + continue; + + c->Message_StringID(0, PICK_LORE); + return; + } + + if (itm) { + c->PushItemOnCursor(*itminst, true); + if ((slotId == MainRange) || (slotId == MainAmmo) || (slotId == MainPrimary) || (slotId == MainSecondary)) + my_bot->SetBotArcher(false); + + my_bot->RemoveBotItemBySlot(slotId, &TempErrorMessage); + if (!TempErrorMessage.empty()) { + c->Message(m_unknown, "Database Error: %s", TempErrorMessage.c_str()); + return; + } + + my_bot->BotRemoveEquipItem(slotId); + my_bot->CalcBotStats(); + } + + switch (slotId) { + case MainCharm: + case MainEar1: + case MainHead: + case MainFace: + case MainEar2: + case MainNeck: + case MainBack: + case MainWrist1: + case MainWrist2: + case MainRange: + case MainPrimary: + case MainSecondary: + case MainFinger1: + case MainFinger2: + case MainChest: + case MainWaist: + case MainPowerSource: + case MainAmmo: + c->Message(m_message, "My %s is %s unequipped", GetBotEquipSlotName(slotId), ((itm) ? ("now") : ("already"))); + break; + case MainShoulders: + case MainArms: + case MainHands: + case MainLegs: + case MainFeet: + c->Message(m_message, "My %s are %s unequipped", GetBotEquipSlotName(slotId), ((itm) ? ("now") : ("already"))); + break; + default: + c->Message(m_fail, "I'm soo confused..."); + break; + } +} + +void bot_subcommand_pet_remove(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_pet_remove", sep->arg[0], "petremove")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); + return; + } + int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, sep->arg[2]) == ActionableBots::ABT_None) + return; + + uint16 class_mask = (PLAYER_CLASS_DRUID_BIT | PLAYER_CLASS_NECROMANCER_BIT | PLAYER_CLASS_ENCHANTER_BIT); + ActionableBots::Filter_ByClasses(c, sbl, class_mask); + if (sbl.empty()) { + c->Message(m_fail, "You have no spawned bots capable of charming"); + return; + } + sbl.remove(nullptr); + + int charmed_pet = 0; + int summoned_pet = 0; + for (auto bot_iter : sbl) { // Probably needs some work to release charmed pets + if (bot_iter->IsBotCharmer()) { + bot_iter->SetBotCharmer(false); + if (sbl.size() == 1) + Bot::BotGroupSay(bot_iter, "Using a summoned pet"); + ++summoned_pet; + continue; + } + + if (bot_iter->GetPet()) { + bot_iter->GetPet()->Say_StringID(PET_GETLOST_STRING); + bot_iter->GetPet()->Depop(false); + bot_iter->SetPetID(0); + } + bot_iter->SetBotCharmer(true); + if (sbl.size() == 1) + Bot::BotGroupSay(bot_iter, "Available for Charming"); + ++charmed_pet; + } + + if (sbl.size() != 1) + c->Message(m_action, "%i of your bots set for charming, %i of your bots set for summoned pet use", charmed_pet, summoned_pet); +} + +void bot_subcommand_pet_set_type(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_pet_set_type", sep->arg[0], "petsettype")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [type: water | fire | air | earth | monster] ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); + c->Message(m_note, "requires one of the following bot classes:"); + c->Message(m_message, "Magician(1)"); + return; + } + int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); // this can be expanded without code modification + + std::string pet_arg = sep->arg[1]; + + uint8 pet_type = 255; + uint8 level_req = 255; + if (!pet_arg.compare("water")) { + pet_type = 0; + level_req = 1; + } + else if (!pet_arg.compare("fire")) { + pet_type = 1; + level_req = 3; + } + else if (!pet_arg.compare("air")) { + pet_type = 2; + level_req = 4; + } + else if (!pet_arg.compare("earth")) { + pet_type = 3; + level_req = 5; + } + else if (!pet_arg.compare("monster")) { + pet_type = 4; + level_req = 30; + } + + if (pet_type == 255) { + c->Message(m_fail, "You must specify a pet [type: water | fire | air | earth | monster]"); + return; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[2], sbl, ab_mask, sep->arg[3]) == ActionableBots::ABT_None) + return; + + uint16 class_mask = PLAYER_CLASS_MAGICIAN_BIT; + ActionableBots::Filter_ByClasses(c, sbl, class_mask); + if (sbl.empty()) { + c->Message(m_fail, "You have no spawned Magician bots"); + return; + } + + ActionableBots::Filter_ByMinLevel(c, sbl, level_req); + if (sbl.empty()) { + c->Message(m_fail, "You have no spawned Magician bots capable of using this pet type: '%s'", pet_arg.c_str()); + return; + } + + uint16 reclaim_energy_id = 331; + for (auto bot_iter : sbl) { + if (!bot_iter) + continue; + + bot_iter->SetPetChooser(true); + bot_iter->SetPetChooserID(pet_type); + if (bot_iter->GetPet()) { + auto pet_id = bot_iter->GetPetID(); + bot_iter->SetPetID(0); + bot_iter->CastSpell(reclaim_energy_id, pet_id); + } + } +} + +void bot_subcommand_portal(Client *c, const Seperator *sep) +{ + bcst_list* local_list = &bot_command_spells[BCEnum::SpT_Depart]; + if (helper_spell_list_fail(c, local_list, BCEnum::SpT_Depart) || helper_command_alias_fail(c, "bot_subcommand_portal", sep->arg[0], "portal")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: %s [list | destination] ([option: single])", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Depart, WIZARD); + return; + } + + bool single = false; + std::string single_arg = sep->arg[2]; + if (!single_arg.compare("single")) + single = true; + + std::string destination = sep->arg[1]; + if (!destination.compare("list")) { + auto my_wizard_bot = ActionableBots::AsGroupMember_ByClass(c, c, WIZARD); + helper_command_depart_list(c, nullptr, my_wizard_bot, local_list, single); + return; + } + else if (destination.empty()) { + c->Message(m_fail, "A [destination] or [list] argument is required to use this command"); + return; + } + + ActionableTarget::Types actionable_targets; + Bot* my_bot = nullptr; + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + bool cast_success = false; + for (auto list_iter : *local_list) { + auto local_entry = list_iter->SafeCastToDepart(); + if (helper_spell_check_fail(local_entry)) + continue; + if (local_entry->caster_class != WIZARD) + continue; + if (local_entry->single != single) + continue; + if (destination.compare(spells[local_entry->spell_id].teleport_zone)) + continue; + + auto target_mob = actionable_targets.Select(c, local_entry->target_type, FRIENDLY); + if (!target_mob) + continue; + + my_bot = ActionableBots::Select_ByMinLevelAndClass(c, local_entry->target_type, sbl, local_entry->spell_level, local_entry->caster_class, target_mob); + if (!my_bot) + continue; + + cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); + break; + } + + helper_no_available_bots(c, my_bot); +} + + +/* + * bot command helpers go below here + */ +bool helper_bot_appearance_fail(Client *bot_owner, Bot *my_bot, BCEnum::AFType fail_type, const char* type_desc) +{ + switch (fail_type) { + case BCEnum::AFT_Value: + bot_owner->Message(m_fail, "Failed to change '%s' for %s due to invalid value for this command", type_desc, my_bot->GetCleanName()); + return true; + case BCEnum::AFT_GenderRace: + bot_owner->Message(m_fail, "Failed to change '%s' for %s due to invalid bot gender and/or race for this command", type_desc, my_bot->GetCleanName()); + return true; + case BCEnum::AFT_Race: + bot_owner->Message(m_fail, "Failed to change '%s' for %s due to invalid bot race for this command", type_desc, my_bot->GetCleanName()); + return true; + default: + return false; + } +} + +void helper_bot_appearance_form_final(Client *bot_owner, Bot *my_bot) +{ + if (!MyBots::IsMyBot(bot_owner, my_bot)) + return; + if (!my_bot->Save()) { + bot_owner->Message(m_unknown, "Failed to save appearance change for %s due to unknown cause...", my_bot->GetCleanName()); + return; + } + + helper_bot_appearance_form_update(my_bot); + bot_owner->Message(m_action, "Successfully changed appearance for %s!", my_bot->GetCleanName()); +} + +void helper_bot_appearance_form_update(Bot *my_bot) +{ + if (!my_bot) + return; + + my_bot->SendIllusionPacket( + my_bot->GetRace(), + my_bot->GetGender(), + 0xFF, //my_bot->GetTexture(), // 0xFF - change back if issues arise + 0xFF, //my_bot->GetHelmTexture(), // 0xFF - change back if issues arise + my_bot->GetHairColor(), + my_bot->GetBeardColor(), + my_bot->GetEyeColor1(), + my_bot->GetEyeColor2(), + my_bot->GetHairStyle(), + my_bot->GetLuclinFace(), + my_bot->GetBeard(), + 0xFF, // aa_title (0xFF) + my_bot->GetDrakkinHeritage(), + my_bot->GetDrakkinTattoo(), + my_bot->GetDrakkinDetails(), + my_bot->GetSize() + ); +} + +uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_class, uint16 bot_race, uint8 bot_gender) +{ + uint32 bot_id = 0; + if (!bot_owner) + return bot_id; + if (!Bot::IsValidName(bot_name)) { + bot_owner->Message(m_fail, "'%s' is an invalid name. You may only use characters 'A-Z', 'a-z' and '_'", bot_name.c_str()); + return bot_id; + } + + std::string TempErrorMessage; + + if (!Bot::IsBotNameAvailable(bot_name.c_str(), &TempErrorMessage)) { + if (!TempErrorMessage.empty()) + bot_owner->Message(m_unknown, "Database Error: %s", TempErrorMessage.c_str()); + bot_owner->Message(m_fail, "The name %s is already being used. Please choose a different name", bot_name.c_str()); + return bot_id; + } + + if (!Bot::IsValidRaceClassCombo(bot_race, bot_class)) { + bot_owner->Message(m_fail, "'%s'(%u):'%s'(%u) is an invalid race-class combination", + Bot::RaceIdToString(bot_race).c_str(), bot_race, Bot::ClassIdToString(bot_class).c_str(), bot_class); + return bot_id; + } + + if (bot_gender > FEMALE) { + bot_owner->Message(m_fail, "gender: %u(M), %u(F)", MALE, FEMALE); + return bot_id; + } + + uint32 mbc = RuleI(Bots, CreationLimit); + if (Bot::CreatedBotCount(bot_owner->CharacterID(), &TempErrorMessage) >= mbc) { + if (!TempErrorMessage.empty()) + bot_owner->Message(m_unknown, "Database Error: %s", TempErrorMessage.c_str()); + bot_owner->Message(m_fail, "You have reached the maximum limit of %i bots", mbc); + return bot_id; + } + + auto DefaultNPCTypeStruct = Bot::CreateDefaultNPCTypeStructForBot( + bot_name.c_str(), + "", + bot_owner->GetLevel(), + bot_race, + bot_class, + bot_gender + ); + + auto my_bot = new Bot(DefaultNPCTypeStruct, bot_owner); + + if (!my_bot->Save()) { + bot_owner->Message(m_unknown, "Failed to create '%s' due to unknown cause", my_bot->GetCleanName()); + return bot_id; + } + + bot_owner->Message(m_action, "Successfully created '%s' (id: %u)", my_bot->GetCleanName(), my_bot->GetBotID()); + + bot_id = my_bot->GetBotID(); + safe_delete(my_bot); + + return bot_id; +} + +void helper_bot_out_of_combat(Client *bot_owner, Bot *my_bot) +{ + if (!bot_owner || !my_bot) + return; + + switch (my_bot->GetClass()) { + case WARRIOR: + case CLERIC: + case PALADIN: + case RANGER: + case SHADOWKNIGHT: + case DRUID: + case MONK: + bot_owner->Message(m_unknown, "%s has no out-of-combat behavior defined", my_bot->GetCleanName()); + break; + case BARD: + bot_owner->Message(m_action, "%s will %s use out-of-combat behavior for bard songs", my_bot->GetCleanName(), ((my_bot->GetAltOutOfCombatBehavior()) ? ("now") : ("no longer"))); + break; + case ROGUE: + case SHAMAN: + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + case BEASTLORD: + case BERSERKER: + bot_owner->Message(m_unknown, "%s has no out-of-combat behavior defined", my_bot->GetCleanName()); + break; + default: + break; + bot_owner->Message(m_fail, "Undefined bot class for %s", my_bot->GetCleanName()); + } +} + +bool helper_cast_standard_spell(Bot* casting_bot, Mob* target_mob, int spell_id, bool annouce_cast, uint32* dont_root_before) +{ + if (!casting_bot || !target_mob) + return false; + + casting_bot->InterruptSpell(); + if (annouce_cast) + Bot::BotGroupSay(casting_bot, "Attempting to cast '%s' on %s", spells[spell_id].name, target_mob->GetCleanName()); + + return casting_bot->CastSpell(spell_id, target_mob->GetID(), 1, -1, -1, dont_root_before); +} + +bool helper_command_alias_fail(Client *bot_owner, const char* command_handler, const char *alias, const char *command) +{ + auto alias_iter = bot_command_aliases.find(&alias[1]); + if (alias_iter == bot_command_aliases.end() || alias_iter->second.compare(command)) { + bot_owner->Message(m_fail, "Undefined linker usage in %s (%s)", command_handler, &alias[1]); + return true; + } + + return false; +} + +void helper_command_depart_list(Client* bot_owner, Bot* druid_bot, Bot* wizard_bot, bcst_list* local_list, bool single_flag) +{ + if (!bot_owner) + return; + + if (!MyBots::IsMyBot(bot_owner, druid_bot)) + druid_bot = nullptr; + if (!MyBots::IsMyBot(bot_owner, wizard_bot)) + wizard_bot = nullptr; + if (!druid_bot && !wizard_bot) { + bot_owner->Message(m_fail, "No bots are capable of performing this action"); + return; + } + + bot_owner->Message(m_message, "The following destinations are available:"); + if (!local_list) { + bot_owner->Message(m_fail, "None"); + return; + } + + std::string msg; + std::string text_link; + + int destinations = 0; + for (auto list_iter : *local_list) { + auto local_entry = list_iter->SafeCastToDepart(); + if (!local_entry) + continue; + + if (druid_bot && druid_bot->GetClass() == local_entry->caster_class && druid_bot->GetLevel() >= local_entry->spell_level) { + if (local_entry->single != single_flag) + continue; + msg = StringFormat("%ccircle %s%s", BOT_COMMAND_CHAR, spells[local_entry->spell_id].teleport_zone, ((single_flag) ? (" single") : (""))); + text_link = druid_bot->CreateSayLink(bot_owner, msg.c_str(), local_entry->long_name.c_str()); + Bot::BotGroupSay(druid_bot, "dest: '%s' click: %s", spells[local_entry->spell_id].teleport_zone, text_link.c_str()); + ++destinations; + continue; + } + if (wizard_bot && wizard_bot->GetClass() == local_entry->caster_class && wizard_bot->GetLevel() >= local_entry->spell_level) { + if (local_entry->single != single_flag) + continue; + msg = StringFormat("%cportal %s%s", BOT_COMMAND_CHAR, spells[local_entry->spell_id].teleport_zone, ((single_flag) ? (" single") : (""))); + text_link = wizard_bot->CreateSayLink(bot_owner, msg.c_str(), local_entry->long_name.c_str()); + Bot::BotGroupSay(wizard_bot, "dest: '%s' click: %s", spells[local_entry->spell_id].teleport_zone, text_link.c_str()); + ++destinations; + continue; + } + } + if (!destinations) + bot_owner->Message(m_fail, "None"); +} + +bool helper_is_help_or_usage(const char* arg) +{ + if (!arg) + return false; + if (strcasecmp(arg, "help") && strcasecmp(arg, "usage")) + return false; + + return true; +} + +bool helper_no_available_bots(Client *bot_owner, Bot *my_bot) +{ + if (!bot_owner) + return true; + if (!my_bot) { + bot_owner->Message(m_fail, "No bots are capable of performing this action"); + return true; + } + + return false; +} + +void helper_send_available_subcommands(Client *bot_owner, const char* command_simile, const std::list& subcommand_list) +{ + bot_owner->Message(m_message, "Available %s management subcommands:", command_simile); + + int bot_subcommands_shown = 0; + for (const auto subcommand_iter : subcommand_list) { + auto find_iter = bot_command_list.find(subcommand_iter); + if (find_iter == bot_command_list.end()) + continue; + if (bot_owner->Admin() < find_iter->second->access) + continue; + + bot_owner->Message(m_usage, "%c%s - %s", BOT_COMMAND_CHAR, subcommand_iter, ((find_iter != bot_command_list.end()) ? (find_iter->second->desc) : ("[no description]"))); + ++bot_subcommands_shown; + } + + bot_owner->Message(m_message, "%d bot subcommand%s listed.", bot_subcommands_shown, bot_subcommands_shown != 1 ? "s" : ""); +} + +void helper_send_usage_required_bots(Client *bot_owner, BCEnum::SpType spell_type, uint8 bot_class) +{ + bot_owner->Message(m_note, "requires one of the following bot classes:"); + if (bot_class) + bot_owner->Message(m_message, "%s", required_bots_map_by_class[spell_type][bot_class].c_str()); + else + bot_owner->Message(m_message, "%s", required_bots_map[spell_type].c_str()); +} + +bool helper_spell_check_fail(STBaseEntry* local_entry) +{ + if (!local_entry) + return true; + if (spells[local_entry->spell_id].zonetype && zone->GetZoneType() && !(spells[local_entry->spell_id].zonetype & zone->GetZoneType())) + return true; + + return false; +} + +bool helper_spell_list_fail(Client *bot_owner, bcst_list* spell_list, BCEnum::SpType spell_type) +{ + if (!spell_list || spell_list->empty()) { + bot_owner->Message(m_fail, "%s", required_bots_map[spell_type].c_str()); + return true; + } + + return false; +} diff --git a/zone/bot_command.h b/zone/bot_command.h new file mode 100644 index 000000000..5d4be85fd --- /dev/null +++ b/zone/bot_command.h @@ -0,0 +1,667 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef BOT_COMMAND_H +#define BOT_COMMAND_H + +class Client; +class Seperator; + +#include "../common/types.h" +#include "bot.h" + + +class BCEnum +{ +public: + typedef enum SpellType { + SpT_None = 0, + SpT_BindAffinity, + SpT_Charm, + SpT_Cure, + SpT_Depart, + SpT_Escape, + SpT_Identify, + SpT_Invisibility, + SpT_Levitation, + SpT_Lull, + SpT_Mesmerize, + SpT_MovementSpeed, + SpT_Resistance, + SpT_Resurrect, + SpT_Rune, + SpT_SendHome, + SpT_Size, + SpT_Stance, + SpT_SummonCorpse, + SpT_WaterBreathing + } SpType; + static const int SpellTypeFirst = SpT_BindAffinity; + static const int SpellTypeLast = SpT_WaterBreathing; + + typedef enum TargetType { + TT_None = 0, + TT_Corpse, + TT_Self, + TT_Animal, + TT_Undead, + TT_Summoned, + TT_Plant, + TT_Single, + TT_GroupV1, + TT_GroupV2, + TT_AECaster, + TT_AEBard, + TT_AETarget + } TType; + static const int TargetTypeFirst = TT_Corpse; + static const int TargetTypeLast = TT_AETarget; + static const int TargetTypeCount = 13; + + typedef enum TargetMask { + TM_None = 0, + TM_Corpse = 1, + TM_Self = 2, + TM_Animal = 4, + TM_Undead = 8, + TM_Summoned = 16, + TM_Plant = 32, + TM_Single = 124, // currently, 2^6 + 2^{2..5}) -or- (64+32+16+8+4) + TM_GroupV1 = 128, + TM_GroupV2 = 256, + TM_AECaster = 512, + TM_AEBard = 1024, + TM_AETarget = 2048 + } TMask; + + typedef enum AppearanceFailType { + AFT_None = 0, + AFT_Value, + AFT_GenderRace, + AFT_Race + } AFType; + + typedef enum AilmentType { + AT_None = 0, + AT_Blindness, // SE: 20 + AT_Disease, // SE: 35 + AT_Poison, // SE: 36 + AT_Curse, // SE: 116 + AT_Corruption // SE: 369 + } AType; + static const int AilmentTypeCount = 5; + + typedef enum InvisibilityType { + IT_None = 0, + IT_Animal, + IT_Undead, + IT_Living, + IT_See + } IType; + + typedef enum ResistanceType { + RT_None = 0, + RT_Fire, // SE: 46 + RT_Cold, // SE: 47 + RT_Poison, // SE: 48 + RT_Disease, // SE: 49 + RT_Magic, // SE: 50 + RT_Corruption // SE: 370 + } RType; + static const int ResistanceTypeCount = 6; + + typedef enum SizeType { + SzT_None = 0, + SzT_Enlarge, + SzT_Reduce + } SzType; + + typedef enum StanceType { + StT_None = 0, + StT_Aggressive, + StT_Defensive + } StType; + + static std::string SpellTypeEnumToString(BCEnum::SpType spell_type) { + switch (spell_type) { + case SpT_BindAffinity: + return "SpT_BindAffinity"; + case SpT_Charm: + return "SpT_Charm"; + case SpT_Cure: + return "SpT_Cure"; + case SpT_Depart: + return "SpT_Depart"; + case SpT_Escape: + return "SpT_Escape"; + case SpT_Identify: + return "SpT_Identify"; + case SpT_Invisibility: + return "SpT_Invisibility"; + case SpT_Levitation: + return "SpT_Levitation"; + case SpT_Lull: + return "SpT_Lull"; + case SpT_Mesmerize: + return "SpT_Mesmerize"; + case SpT_MovementSpeed: + return "SpT_MovementSpeed"; + case SpT_Resistance: + return "SpT_Resistance"; + case SpT_Resurrect: + return "SpT_Resurrect"; + case SpT_Rune: + return "SpT_Rune"; + case SpT_SendHome: + return "SpT_SendHome"; + case SpT_Size: + return "SpT_Size"; + case SpT_Stance: + return "SpT_Stance"; + case SpT_SummonCorpse: + return "SpT_SummonCorpse"; + case SpT_WaterBreathing: + return "SpT_WaterBreathing"; + default: + return "SpT_None"; + } + } + + static std::string TargetTypeEnumToString(BCEnum::TType target_type) { + switch (target_type) { + case TT_Self: + return "TT_Self"; + case TT_Animal: + return "TT_Animal"; + case TT_Undead: + return "TT_Undead"; + case TT_Summoned: + return "TT_Summoned"; + case TT_Plant: + return "TT_Plant"; + case TT_Single: + return "TT_Single"; + case TT_GroupV1: + return "TT_GroupV1"; + case TT_GroupV2: + return "TT_GroupV2"; + case TT_AECaster: + return "TT_AECaster"; + case TT_AEBard: + return "TT_AEBard"; + case TT_AETarget: + return "TT_AETarget"; + case TT_Corpse: + return "TT_Corpse"; + default: + return "TT_None"; + } + } +}; + + +class STBaseEntry; +class STCharmEntry; +class STCureEntry; +class STDepartEntry; +class STEscapeEntry; +class STInvisibilityEntry; +class STMovementSpeedEntry; +class STResistanceEntry; +class STResurrectEntry; +class STSendHomeEntry; +class STSizeEntry; +class STStanceEntry; + +class STBaseEntry +{ +protected: + BCEnum::SpType m_bcst; + +public: + int spell_id; + uint8 spell_level; + uint8 caster_class; + BCEnum::TType target_type; + + // A non-polymorphic constructor requires an appropriate, non-'ST_None' BCEnum::SType + STBaseEntry(BCEnum::SpType init_bcst = BCEnum::SpT_None) { + spell_id = 0; + spell_level = 255; + caster_class = 255; + target_type = BCEnum::TT_None; + m_bcst = init_bcst; + } + STBaseEntry(STBaseEntry* prototype) { + spell_id = prototype->spell_id; + spell_level = 255; + caster_class = 255; + target_type = prototype->target_type; + m_bcst = prototype->BCST(); + } + virtual ~STBaseEntry() { return; }; + + BCEnum::SpType BCST() { return m_bcst; } + + virtual bool IsDerived() { return false; } + + bool IsCharm() const { return (m_bcst == BCEnum::SpT_Charm); } + bool IsCure() const { return (m_bcst == BCEnum::SpT_Cure); } + bool IsDepart() const { return (m_bcst == BCEnum::SpT_Depart); } + bool IsEscape() const { return (m_bcst == BCEnum::SpT_Escape); } + bool IsInvisibility() const { return (m_bcst == BCEnum::SpT_Invisibility); } + bool IsMovementSpeed() const { return (m_bcst == BCEnum::SpT_MovementSpeed); } + bool IsResistance() const { return (m_bcst == BCEnum::SpT_Resistance); } + bool IsResurrect() const { return (m_bcst == BCEnum::SpT_Resurrect); } + bool IsSendHome() const { return (m_bcst == BCEnum::SpT_SendHome); } + bool IsSize() const { return (m_bcst == BCEnum::SpT_Size); } + bool IsStance() const { return (m_bcst == BCEnum::SpT_Stance); } + + virtual STCharmEntry* SafeCastToCharm() { return nullptr; } + virtual STCureEntry* SafeCastToCure() { return nullptr; } + virtual STDepartEntry* SafeCastToDepart() { return nullptr; } + virtual STEscapeEntry* SafeCastToEscape() { return nullptr; } + virtual STInvisibilityEntry* SafeCastToInvisibility() { return nullptr; } + virtual STMovementSpeedEntry* SafeCastToMovementSpeed() { return nullptr; } + virtual STResistanceEntry* SafeCastToResistance() { return nullptr; } + virtual STResurrectEntry* SafeCastToResurrect() { return nullptr; } + virtual STSendHomeEntry* SafeCastToSendHome() { return nullptr; } + virtual STSizeEntry* SafeCastToSize() { return nullptr; } + virtual STStanceEntry* SafeCastToStance() { return nullptr; } +}; + +class STCharmEntry : public STBaseEntry +{ +public: + bool dire; + + STCharmEntry() { + m_bcst = BCEnum::SpT_Charm; + dire = false; + } + STCharmEntry(STCharmEntry* prototype) : STBaseEntry(prototype) { + m_bcst = BCEnum::SpT_Charm; + dire = prototype->dire; + } + virtual ~STCharmEntry() { return; }; + + virtual bool IsDerived() { return true; } + + virtual STCharmEntry* SafeCastToCharm() { return ((m_bcst == BCEnum::SpT_Charm) ? (static_cast(this)) : (nullptr)); } +}; + +class STCureEntry : public STBaseEntry +{ +public: + int cure_value[BCEnum::AilmentTypeCount]; + int cure_total; + + STCureEntry() { + m_bcst = BCEnum::SpT_Cure; + memset(&cure_value, 0, (sizeof(int) * BCEnum::AilmentTypeCount)); + cure_total = 0; + } + STCureEntry(STCureEntry* prototype) : STBaseEntry(prototype) { + m_bcst = BCEnum::SpT_Cure; + memcpy(&cure_value, prototype->cure_value, (sizeof(int) * BCEnum::AilmentTypeCount)); + cure_total = prototype->cure_total; + } + virtual ~STCureEntry() { return; }; + + virtual bool IsDerived() { return true; } + + virtual STCureEntry* SafeCastToCure() { return ((m_bcst == BCEnum::SpT_Cure) ? (static_cast(this)) : (nullptr)); } +}; + +class STDepartEntry : public STBaseEntry +{ +public: + bool single; + std::string long_name; + + STDepartEntry() { + m_bcst = BCEnum::SpT_Depart; + single = false; + long_name.clear(); + } + STDepartEntry(STDepartEntry* prototype) : STBaseEntry(prototype) { + m_bcst = BCEnum::SpT_Depart; + single = prototype->single; + long_name = prototype->long_name; + } + virtual ~STDepartEntry() { return; }; + + virtual bool IsDerived() { return true; } + + virtual STDepartEntry* SafeCastToDepart() { return ((m_bcst == BCEnum::SpT_Depart) ? (static_cast(this)) : (nullptr)); } +}; + +class STEscapeEntry : public STBaseEntry +{ +public: + bool lesser; + + STEscapeEntry() { + m_bcst = BCEnum::SpT_Escape; + lesser = false; + } + STEscapeEntry(STEscapeEntry* prototype) : STBaseEntry(prototype) { + m_bcst = BCEnum::SpT_Escape; + lesser = prototype->lesser; + } + virtual ~STEscapeEntry() { return; }; + + virtual bool IsDerived() { return true; } + + virtual STEscapeEntry* SafeCastToEscape() { return ((m_bcst == BCEnum::SpT_Escape) ? (static_cast(this)) : (nullptr)); } +}; + +class STInvisibilityEntry : public STBaseEntry +{ +public: + BCEnum::IType invis_type; + + STInvisibilityEntry() { + m_bcst = BCEnum::SpT_Invisibility; + invis_type = BCEnum::IT_None; + } + STInvisibilityEntry(STInvisibilityEntry* prototype) : STBaseEntry(prototype) { + m_bcst = BCEnum::SpT_Invisibility; + invis_type = prototype->invis_type; + } + virtual ~STInvisibilityEntry() { return; }; + + virtual bool IsDerived() { return true; } + + virtual STInvisibilityEntry* SafeCastToInvisibility() { return ((m_bcst == BCEnum::SpT_Invisibility) ? (static_cast(this)) : (nullptr)); } +}; + +class STMovementSpeedEntry : public STBaseEntry +{ +public: + bool group; + + STMovementSpeedEntry() { + m_bcst = BCEnum::SpT_MovementSpeed; + group = false; + } + STMovementSpeedEntry(STMovementSpeedEntry* prototype) : STBaseEntry(prototype) { + m_bcst = BCEnum::SpT_MovementSpeed; + group = prototype->group; + } + virtual ~STMovementSpeedEntry() { return; }; + + virtual bool IsDerived() { return true; } + + virtual STMovementSpeedEntry* SafeCastToMovementSpeed() { return ((m_bcst == BCEnum::SpT_MovementSpeed) ? (static_cast(this)) : (nullptr)); } +}; + +class STResistanceEntry : public STBaseEntry +{ +public: + int resist_value[BCEnum::ResistanceTypeCount]; + int resist_total; + + STResistanceEntry() { + m_bcst = BCEnum::SpT_Resistance; + memset(&resist_value, 0, (sizeof(int) * BCEnum::ResistanceTypeCount)); + resist_total = 0; + } + STResistanceEntry(STResistanceEntry* prototype) : STBaseEntry(prototype) { + m_bcst = BCEnum::SpT_Resistance; + memcpy(&resist_value, prototype->resist_value, (sizeof(int) * BCEnum::ResistanceTypeCount)); + resist_total = prototype->resist_total; + } + virtual ~STResistanceEntry() { return; }; + + virtual bool IsDerived() { return true; } + + virtual STResistanceEntry* SafeCastToResistance() { return ((m_bcst == BCEnum::SpT_Resistance) ? (static_cast(this)) : (nullptr)); } +}; + +class STResurrectEntry : public STBaseEntry +{ +public: + bool aoe; + + STResurrectEntry() { + m_bcst = BCEnum::SpT_Resurrect; + aoe = false; + } + STResurrectEntry(STResurrectEntry* prototype) : STBaseEntry(prototype) { + m_bcst = BCEnum::SpT_Resurrect; + aoe = prototype->aoe; + } + virtual ~STResurrectEntry() { return; }; + + virtual bool IsDerived() { return true; } + + virtual STResurrectEntry* SafeCastToResurrect() { return ((m_bcst == BCEnum::SpT_Resurrect) ? (static_cast(this)) : (nullptr)); } +}; + +class STSendHomeEntry : public STBaseEntry +{ +public: + bool group; + + STSendHomeEntry() { + m_bcst = BCEnum::SpT_SendHome; + group = false; + } + STSendHomeEntry(STSendHomeEntry* prototype) : STBaseEntry(prototype) { + m_bcst = BCEnum::SpT_SendHome; + group = prototype->group; + } + virtual ~STSendHomeEntry() { return; }; + + virtual bool IsDerived() { return true; } + + virtual STSendHomeEntry* SafeCastToSendHome() { return ((m_bcst == BCEnum::SpT_SendHome) ? (static_cast(this)) : (nullptr)); } +}; + +class STSizeEntry : public STBaseEntry +{ +public: + BCEnum::SzType size_type; + + STSizeEntry() { + m_bcst = BCEnum::SpT_Size; + size_type = BCEnum::SzT_None; + } + STSizeEntry(STSizeEntry* prototype) : STBaseEntry(prototype) { + m_bcst = BCEnum::SpT_Size; + size_type = prototype->size_type; + } + virtual ~STSizeEntry() { return; }; + + virtual bool IsDerived() { return true; } + + virtual STSizeEntry* SafeCastToSize() { return ((m_bcst == BCEnum::SpT_Size) ? (static_cast(this)) : (nullptr)); } +}; + +class STStanceEntry : public STBaseEntry { +public: + BCEnum::StType stance_type; + + STStanceEntry() { + m_bcst = BCEnum::SpT_Stance; + stance_type = BCEnum::StT_None; + } + STStanceEntry(STStanceEntry* prototype) : STBaseEntry(prototype) { + m_bcst = BCEnum::SpT_Stance; + stance_type = prototype->stance_type; + } + virtual ~STStanceEntry() { return; }; + + virtual bool IsDerived() { return true; } + + virtual STStanceEntry* SafeCastToStance() { return ((m_bcst == BCEnum::SpT_Stance) ? (static_cast(this)) : (nullptr)); } +}; + + +typedef std::list bcst_list; +typedef std::map bcst_map; + +typedef std::map bcst_required_bot_classes_map; +typedef std::map> bcst_required_bot_classes_map_by_class; + +typedef std::map bcst_levels; +typedef std::map bcst_levels_map; + + +#define BOT_COMMAND_CHAR '^' + +typedef void (*BotCmdFuncPtr)(Client *,const Seperator *); + +typedef struct { + int access; + const char *desc; // description of bot command + BotCmdFuncPtr function; // null means perl function +} BotCommandRecord; + +extern int (*bot_command_dispatch)(Client *,char const*); +extern int bot_command_count; // number of bot commands loaded + + +// the bot command system: +int bot_command_init(void); +void bot_command_deinit(void); +int bot_command_add(std::string bot_command_name, const char *desc, int access, BotCmdFuncPtr function); +int bot_command_not_avail(Client *c, const char *message); +int bot_command_real_dispatch(Client *c, char const *message); +void bot_command_log_command(Client *c, const char *message); + + +// bot commands +void bot_command_actionable(Client *c, const Seperator *sep); +void bot_command_aggressive(Client *c, const Seperator *sep); +void bot_command_attack(Client *c, const Seperator *sep); +void bot_command_bind_affinity(Client *c, const Seperator *sep); +void bot_command_bot(Client *c, const Seperator *sep); +void bot_command_botgroup(Client *c, const Seperator *sep); +void bot_command_charm(Client *c, const Seperator *sep); +void bot_command_cure(Client *c, const Seperator *sep); +void bot_command_defensive(Client *c, const Seperator *sep); +void bot_command_depart(Client *c, const Seperator *sep); +void bot_command_escape(Client *c, const Seperator *sep); +void bot_command_find_aliases(Client *c, const Seperator *sep); +void bot_command_follow(Client *c, const Seperator *sep); +void bot_command_guard(Client *c, const Seperator *sep); +void bot_command_heal_rotation(Client *c, const Seperator *sep); +void bot_command_help(Client *c, const Seperator *sep); +void bot_command_hold(Client *c, const Seperator *sep); +void bot_command_identify(Client *c, const Seperator *sep); +void bot_command_inventory(Client *c, const Seperator *sep); +void bot_command_invisibility(Client *c, const Seperator *sep); +void bot_command_levitation(Client *c, const Seperator *sep); +void bot_command_lull(Client *c, const Seperator *sep); +void bot_command_mesmerize(Client *c, const Seperator *sep); +void bot_command_movement_speed(Client *c, const Seperator *sep); +void bot_command_pet(Client *c, const Seperator *sep); +void bot_command_pick_lock(Client *c, const Seperator *sep); +void bot_command_pull(Client *c, const Seperator *sep); +void bot_command_release(Client *c, const Seperator *sep); +void bot_command_resistance(Client *c, const Seperator *sep); +void bot_command_resurrect(Client *c, const Seperator *sep); +void bot_command_rune(Client *c, const Seperator *sep); +void bot_command_send_home(Client *c, const Seperator *sep); +void bot_command_size(Client *c, const Seperator *sep); +void bot_command_summon_corpse(Client *c, const Seperator *sep); +void bot_command_taunt(Client *c, const Seperator *sep); +void bot_command_track(Client *c, const Seperator *sep); +void bot_command_water_breathing(Client *c, const Seperator *sep); + + +// bot subcommands +void bot_subcommand_bot_appearance(Client *c, const Seperator *sep); +void bot_subcommand_bot_beard_color(Client *c, const Seperator *sep); +void bot_subcommand_bot_beard_style(Client *c, const Seperator *sep); +void bot_subcommand_bot_camp(Client *c, const Seperator *sep); +void bot_subcommand_bot_clone(Client *c, const Seperator *sep); +void bot_subcommand_bot_create(Client *c, const Seperator *sep); +void bot_subcommand_bot_delete(Client *c, const Seperator *sep); +void bot_subcommand_bot_details(Client *c, const Seperator *sep); +void bot_subcommand_bot_dye_armor(Client *c, const Seperator *sep); +void bot_subcommand_bot_eyes(Client *c, const Seperator *sep); +void bot_subcommand_bot_face(Client *c, const Seperator *sep); +void bot_subcommand_bot_follow_distance(Client *c, const Seperator *sep); +void bot_subcommand_bot_hair_color(Client *c, const Seperator *sep); +void bot_subcommand_bot_hairstyle(Client *c, const Seperator *sep); +void bot_subcommand_bot_heritage(Client *c, const Seperator *sep); +void bot_subcommand_bot_inspect_message(Client *c, const Seperator *sep); +void bot_subcommand_bot_list(Client *c, const Seperator *sep); +void bot_subcommand_bot_out_of_combat(Client *c, const Seperator *sep); +void bot_subcommand_bot_report(Client *c, const Seperator *sep); +void bot_subcommand_bot_spawn(Client *c, const Seperator *sep); +void bot_subcommand_bot_stance(Client *c, const Seperator *sep); +void bot_subcommand_bot_summon(Client *c, const Seperator *sep); +void bot_subcommand_bot_tattoo(Client *c, const Seperator *sep); +void bot_subcommand_bot_toggle_archer(Client *c, const Seperator *sep); +void bot_subcommand_bot_toggle_helm(Client *c, const Seperator *sep); +void bot_subcommand_bot_update(Client *c, const Seperator *sep); +void bot_subcommand_bot_woad(Client *c, const Seperator *sep); +void bot_subcommand_botgroup_add_member(Client *c, const Seperator *sep); +void bot_subcommand_botgroup_create(Client *c, const Seperator *sep); +void bot_subcommand_botgroup_delete(Client *c, const Seperator *sep); +void bot_subcommand_botgroup_list(Client *c, const Seperator *sep); +void bot_subcommand_botgroup_load(Client *c, const Seperator *sep); +void bot_subcommand_botgroup_remove_member(Client *c, const Seperator *sep); +void bot_subcommand_circle(Client *c, const Seperator *sep); +void bot_subcommand_evacuate(Client *c, const Seperator *sep); +void bot_subcommand_heal_rotation_adaptive_targeting(Client *c, const Seperator *sep); +void bot_subcommand_heal_rotation_add_member(Client *c, const Seperator *sep); +void bot_subcommand_heal_rotation_add_target(Client *c, const Seperator *sep); +void bot_subcommand_heal_rotation_adjust_critical(Client *c, const Seperator *sep); +void bot_subcommand_heal_rotation_adjust_safe(Client *c, const Seperator *sep); +void bot_subcommand_heal_rotation_casting_override(Client *c, const Seperator *sep); +void bot_subcommand_heal_rotation_change_interval(Client *c, const Seperator *sep); +void bot_subcommand_heal_rotation_clear_targets(Client *c, const Seperator *sep); +void bot_subcommand_heal_rotation_create(Client *c, const Seperator *sep); +void bot_subcommand_heal_rotation_fast_heals(Client *c, const Seperator *sep); +void bot_subcommand_heal_rotation_list(Client *c, const Seperator *sep); +void bot_subcommand_heal_rotation_remove_member(Client *c, const Seperator *sep); +void bot_subcommand_heal_rotation_remove_target(Client *c, const Seperator *sep); +void bot_subcommand_heal_rotation_reset_limits(Client *c, const Seperator *sep); +void bot_subcommand_heal_rotation_start(Client *c, const Seperator *sep); +void bot_subcommand_heal_rotation_stop(Client *c, const Seperator *sep); +void bot_subcommand_inventory_give(Client *c, const Seperator *sep); +void bot_subcommand_inventory_list(Client *c, const Seperator *sep); +void bot_subcommand_inventory_remove(Client *c, const Seperator *sep); +void bot_subcommand_pet_remove(Client *c, const Seperator *sep); +void bot_subcommand_pet_set_type(Client *c, const Seperator *sep); +void bot_subcommand_portal(Client *c, const Seperator *sep); +void bot_subcommand_succor(Client *c, const Seperator *sep); + + +// bot command helpers +bool helper_bot_appearance_fail(Client *bot_owner, Bot *my_bot, BCEnum::AFType fail_type, const char* type_desc); +void helper_bot_appearance_form_final(Client *bot_owner, Bot *my_bot); +void helper_bot_appearance_form_update(Bot *my_bot); +uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_class, uint16 bot_race, uint8 bot_gender); +void helper_bot_out_of_combat(Client *bot_owner, Bot *my_bot); +bool helper_cast_standard_spell(Bot* casting_bot, Mob* target_mob, int spell_id, bool annouce_cast = true, uint32* dont_root_before = nullptr); +bool helper_command_alias_fail(Client *bot_owner, const char* command_handler, const char *alias, const char *command); +void helper_command_depart_list(Client* bot_owner, Bot* druid_bot, Bot* wizard_bot, bcst_list* local_list, bool single_flag = false); +bool helper_is_help_or_usage(const char* arg); +bool helper_no_available_bots(Client *bot_owner, Bot *my_bot = nullptr); +void helper_send_available_subcommands(Client *bot_owner, const char* command_simile, const std::list& subcommand_list); +void helper_send_usage_required_bots(Client *bot_owner, BCEnum::SpType spell_type, uint8 bot_class = 0); +bool helper_spell_check_fail(STBaseEntry* local_entry); +bool helper_spell_list_fail(Client *bot_owner, bcst_list* spell_list, BCEnum::SpType spell_type); +#endif diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp new file mode 100644 index 000000000..c15d583ae --- /dev/null +++ b/zone/bot_database.cpp @@ -0,0 +1,732 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "../common/global_define.h" +#include "../common/rulesys.h" +#include "../common/string_util.h" +#include "../common/eqemu_logsys.h" + +#include "bot_database.h" +#include "bot.h" + +BotDatabase botdb; + + +BotDatabase::BotDatabase() +{ + +} + +BotDatabase::BotDatabase(const char* host, const char* user, const char* passwd, const char* database, uint32 port) +{ + Connect(host, user, passwd, database, port); +} + +BotDatabase::~BotDatabase() +{ + +} + +bool BotDatabase::Connect(const char* host, const char* user, const char* passwd, const char* database, uint32 port) { + uint32 errnum = 0; + char errbuf[MYSQL_ERRMSG_SIZE]; + if (!Open(host, user, passwd, database, port, &errnum, errbuf)) { + Log.Out(Logs::General, Logs::Error, "Failed to connect to bot database: Error: %s", errbuf); + return false; + } + else { + Log.Out(Logs::General, Logs::Status, "Using bot database '%s' at %s:%d", database, host, port); + return true; + } +} + +bool BotDatabase::GetCommandSettings(std::map>> &bot_command_settings) +{ + bot_command_settings.clear(); + + std::string query = "SELECT `bot_command`, `access`, `aliases` FROM `bot_command_settings`"; + auto results = QueryDatabase(query); + if (!results.Success()) + return false; + + for (auto row = results.begin(); row != results.end(); ++row) { + bot_command_settings[row[0]].first = atoi(row[1]); + if (row[2][0] == 0) + continue; + + auto aliases = SplitString(row[2], '|'); + for (auto iter : aliases) { + if (!iter.empty()) + bot_command_settings[row[0]].second.push_back(iter); + } + } + + return true; +} + + +// Bot command functions +bool BotDatabase::GetInspectMessage(uint32 bot_id, InspectMessage_Struct* message) +{ + std::string query = StringFormat("SELECT `inspect_message` FROM `bot_inspect_messages` WHERE `bot_id` = %i LIMIT 1", bot_id); + auto results = QueryDatabase(query); + if (!results.Success()) + return false; + + auto row = results.begin(); + memset(message, '\0', sizeof(InspectMessage_Struct)); + for (auto row = results.begin(); row != results.end(); ++row) { + memcpy(message, row[0], sizeof(InspectMessage_Struct)); + } + + return true; +} + +bool BotDatabase::SetInspectMessage(uint32 bot_id, const InspectMessage_Struct* message) +{ + std::string query = StringFormat("REPLACE INTO `bot_inspect_messages` (bot_id, inspect_message) VALUES (%u, '%s')", bot_id, EscapeString(message->text).c_str()); + auto results = QueryDatabase(query); + + return results.Success(); +} + +bool BotDatabase::SetAllInspectMessages(uint32 owner_id, const InspectMessage_Struct* message) +{ + std::string query = StringFormat( + "UPDATE `bot_inspect_messages`" + " SET `inspect_message` = '%s'" + " WHERE `bot_id`" + " IN (SELECT `bot_id` FROM `bot_data` WHERE `owner_id` = '%u')", + EscapeString(message->text).c_str(), owner_id + ); + auto results = QueryDatabase(query); + + return results.Success(); +} + +bool BotDatabase::SetAllArmorColorBySlot(uint32 owner_id, int16 slot_id, uint32 rgb_value) +{ + if (!owner_id) + return false; + + std::string query = StringFormat( + "UPDATE `bot_inventories` bi" + " INNER JOIN `bot_data` bd" + " ON bd.`owner_id` = '%u'" + " SET bi.`inst_color` = '%u'" + " WHERE bi.`bot_id` = bd.`bot_id`" + " AND bi.`slot_id` IN (%u, %u, %u, %u, %u, %u, %u, %u)" + " AND bi.`slot_id` = '%i'", + owner_id, + rgb_value, + MainHead, MainChest, MainArms, MainWrist1, MainWrist2, MainHands, MainLegs, MainFeet, + slot_id + ); + auto results = QueryDatabase(query); + + return results.Success(); +} + +bool BotDatabase::SetAllArmorColors(uint32 owner_id, uint32 rgb_value) +{ + if (!owner_id) + return false; + + std::string query = StringFormat( + "UPDATE `bot_inventories` bi" + " INNER JOIN `bot_data` bd" + " ON bd.`owner_id` = '%u'" + " SET bi.`inst_color` = '%u'" + " WHERE bi.`bot_id` = bd.`bot_id`" + " AND bi.`slot_id` IN (%u, %u, %u, %u, %u, %u, %u, %u)", + owner_id, + rgb_value, + MainHead, MainChest, MainArms, MainWrist1, MainWrist2, MainHands, MainLegs, MainFeet + ); + auto results = QueryDatabase(query); + + return results.Success(); +} + +bool BotDatabase::SetHelmAppearance(uint32 owner_id, uint32 bot_id, bool show_flag) +{ + if (!owner_id || !bot_id) + return false; + + std::string query = StringFormat( + "UPDATE `bot_data`" + " SET `show_helm` = '%u'" + " WHERE `owner_id` = '%u'" + " AND `bot_id` = '%u'", + (show_flag ? 1 : 0), + owner_id, + bot_id + ); + auto results = QueryDatabase(query); + + return results.Success(); +} + +bool BotDatabase::SetAllHelmAppearances(uint32 owner_id, bool show_flag) +{ + if (!owner_id) + return false; + + std::string query = StringFormat( + "UPDATE `bot_data`" + " SET `show_helm` = '%u'" + " WHERE `owner_id` = '%u'", + (show_flag ? 1 : 0), + owner_id + ); + auto results = QueryDatabase(query); + + return results.Success(); +} + +bool BotDatabase::ToggleHelmAppearance(uint32 owner_id, uint32 bot_id) +{ + if (!owner_id || !bot_id) + return false; + + std::string query = StringFormat( + "UPDATE `bot_data`" + " SET `show_helm` = (`show_helm` XOR '1')" + " WHERE `owner_id` = '%u'" + " AND `bot_id` = '%u'", + owner_id, + bot_id + ); + auto results = QueryDatabase(query); + + return results.Success(); +} + +bool BotDatabase::ToggleAllHelmAppearances(uint32 owner_id) +{ + if (!owner_id) + return false; + + std::string query = StringFormat( + "UPDATE `bot_data`" + " SET `show_helm` = (`show_helm` XOR '1')" + " WHERE `owner_id` = '%u'", + owner_id + ); + auto results = QueryDatabase(query); + + return results.Success(); +} + +bool BotDatabase::SetFollowDistance(uint32 owner_id, uint32 bot_id, uint32 follow_distance) +{ + if (!owner_id || !bot_id || !follow_distance) + return false; + + std::string query = StringFormat( + "UPDATE `bot_data`" + " SET `follow_distance` = '%u'" + " WHERE `owner_id` = '%u'" + " AND `bot_id` = '%u'", + follow_distance, + owner_id, + bot_id + ); + auto results = QueryDatabase(query); + + return results.Success(); +} + +bool BotDatabase::SetAllFollowDistances(uint32 owner_id, uint32 follow_distance) +{ + if (!owner_id || !follow_distance) + return false; + + std::string query = StringFormat( + "UPDATE `bot_data`" + " SET `follow_distance` = '%u'" + " WHERE `owner_id` = '%u'", + follow_distance, + owner_id + ); + auto results = QueryDatabase(query); + + return results.Success(); +} + +uint32 BotDatabase::Clone(uint32 owner_id, uint32 bot_id, const char* clone_name) +{ + if (!owner_id || !bot_id || !clone_name) + return 0; + + std::string data_query = StringFormat( + "INSERT INTO `bot_data`" + " (" + "`owner_id`," + " `spells_id`," + " `name`," + " `last_name`," + " `title`," + " `suffix`," + " `zone_id`," + " `gender`," + " `race`," + " `class`," + " `level`," + " `deity`," + " `creation_day`," + " `last_spawn`," + " `time_spawned`," + " `size`," + " `face`," + " `hair_color`," + " `hair_style`," + " `beard`," + " `beard_color`," + " `eye_color_1`," + " `eye_color_2`," + " `drakkin_heritage`," + " `drakkin_tattoo`," + " `drakkin_details`," + " `ac`," + " `atk`," + " `hp`," + " `mana`," + " `str`," + " `sta`," + " `cha`," + " `dex`," + " `int`," + " `agi`," + " `wis`," + " `fire`," + " `cold`," + " `magic`," + " `poison`," + " `disease`," + " `corruption`," + " `show_helm`," + " `follow_distance`" + ")" + " SELECT" + " bd.`owner_id`," + " bd.`spells_id`," + " '%s'," + " ''," + " bd.`title`," + " bd.`suffix`," + " bd.`zone_id`," + " bd.`gender`," + " bd.`race`," + " bd.`class`," + " bd.`level`," + " bd.`deity`," + " UNIX_TIMESTAMP()," + " UNIX_TIMESTAMP()," + " '0'," + " bd.`size`," + " bd.`face`," + " bd.`hair_color`," + " bd.`hair_style`," + " bd.`beard`," + " bd.`beard_color`," + " bd.`eye_color_1`," + " bd.`eye_color_2`," + " bd.`drakkin_heritage`," + " bd.`drakkin_tattoo`," + " bd.`drakkin_details`," + " bd.`ac`," + " bd.`atk`," + " bd.`hp`," + " bd.`mana`," + " bd.`str`," + " bd.`sta`," + " bd.`cha`," + " bd.`dex`," + " bd.`int`," + " bd.`agi`," + " bd.`wis`," + " bd.`fire`," + " bd.`cold`," + " bd.`magic`," + " bd.`poison`," + " bd.`disease`," + " bd.`corruption`," + " bd.`show_helm`," + " bd.`follow_distance`" + " FROM `bot_data` bd" + " WHERE" + " bd.`owner_id` = '%u'" + " AND" + " bd.`bot_id` = '%u'", + clone_name, + owner_id, + bot_id + ); + auto results = QueryDatabase(data_query); + if (!results.Success()) + return 0; + + return results.LastInsertedID(); +} + +bool BotDatabase::CloneInventory(uint32 owner_id, uint32 bot_id, uint32 clone_id) +{ + if (!owner_id || !bot_id || !clone_id) + return false; + + std::string inv_query = StringFormat( + "INSERT INTO `bot_inventories`" + " (" + "bot_id," + " `slot_id`," + " `item_id`," + " `inst_charges`," + " `inst_color`," + " `inst_no_drop`," + " `inst_custom_data`," + " `ornament_icon`," + " `ornament_id_file`," + " `ornament_hero_model`," + " `augment_1`," + " `augment_2`," + " `augment_3`," + " `augment_4`," + " `augment_5`," + " `augment_6`" + ")" + " SELECT" + " '%u' bot_id," + " bi.`slot_id`," + " bi.`item_id`," + " bi.`inst_charges`," + " bi.`inst_color`," + " bi.`inst_no_drop`," + " bi.`inst_custom_data`," + " bi.`ornament_icon`," + " bi.`ornament_id_file`," + " bi.`ornament_hero_model`," + " bi.`augment_1`," + " bi.`augment_2`," + " bi.`augment_3`," + " bi.`augment_4`," + " bi.`augment_5`," + " bi.`augment_6`" + " FROM `bot_inventories` bi" + " WHERE" + " bi.`bot_id` = '%u'" + " AND" + " '%u' = (SELECT `owner_id` FROM `bot_data` WHERE `bot_id` = '%u')", + clone_id, + bot_id, + owner_id, + bot_id + ); + auto results = QueryDatabase(inv_query); + + return results.Success(); +} + + +// Bot-group functions +bool BotDatabase::DoesBotGroupExist(std::string& group_name) +{ + if (group_name.empty()) + return false; + + std::string query = StringFormat("SELECT `group_name` FROM `vw_bot_groups` WHERE `group_name` LIKE '%s' LIMIT 1", group_name.c_str()); + auto results = QueryDatabase(query); + if (!results.Success() || results.RowCount() == 0) + return false; + + auto row = results.begin(); + if (!group_name.compare(row[0])) + return true; + + return false; +} + +uint32 BotDatabase::GetGroupIDByGroupName(std::string& group_name, std::string& error_message) +{ + if (group_name.empty()) + return 0; + + std::string query = StringFormat("SELECT `groups_index` FROM `bot_groups` WHERE `group_name` = '%s'", group_name.c_str()); + auto results = QueryDatabase(query); + if (!results.Success() || !results.RowCount()) { + error_message = results.ErrorMessage(); + return 0; + } + auto row = results.begin(); + + return atoi(row[0]); +} + +uint32 BotDatabase::GetLeaderIDByGroupName(std::string& group_name, std::string& error_message) +{ + if (group_name.empty()) + return 0; + + std::string query = StringFormat("SELECT `group_leader_id` FROM `bot_groups` WHERE `group_name` = '%s'", group_name.c_str()); + auto results = QueryDatabase(query); + if (!results.Success() || !results.RowCount()) { + error_message = results.ErrorMessage(); + return 0; + } + auto row = results.begin(); + + return atoi(row[0]); +} + +std::string BotDatabase::GetGroupNameByGroupID(uint32 group_id, std::string& error_message) +{ + if (!group_id) + return std::string(); + + std::string query = StringFormat("SELECT `group_name` FROM `bot_groups` WHERE `groups_index` = '%u'", group_id); + auto results = QueryDatabase(query); + if (!results.Success() || !results.RowCount()) { + error_message = results.ErrorMessage(); + return std::string(); + } + auto row = results.begin(); + + return std::string(row[0]); +} + +std::string BotDatabase::GetGroupNameByLeaderID(uint32 leader_id, std::string& error_message) +{ + if (!leader_id) + return std::string(); + + std::string query = StringFormat("SELECT `group_name` FROM `bot_groups` WHERE `group_leader_id` = '%u'", leader_id); + auto results = QueryDatabase(query); + if (!results.Success() || !results.RowCount()) { + error_message = results.ErrorMessage(); + return std::string(); + } + auto row = results.begin(); + + return std::string(row[0]); +} + +uint32 BotDatabase::GetGroupIDByLeaderID(uint32 leader_id, std::string& error_message) +{ + if (!leader_id) + return 0; + + std::string query = StringFormat("SELECT `groups_index` FROM `bot_groups` WHERE `group_leader_id` = '%u'", leader_id); + auto results = QueryDatabase(query); + if (!results.Success() || !results.RowCount()) { + error_message = results.ErrorMessage(); + return 0; + } + auto row = results.begin(); + + return atoi(row[0]); +} + +uint32 BotDatabase::GetLeaderIDByGroupID(uint32 group_id, std::string& error_message) +{ + if (!group_id) + return 0; + + std::string query = StringFormat("SELECT `group_leader_id` FROM `bot_groups` WHERE `groups_index` = '%u'", group_id); + auto results = QueryDatabase(query); + if (!results.Success() || !results.RowCount()) { + error_message = results.ErrorMessage(); + return 0; + } + auto row = results.begin(); + + return atoi(row[0]); +} + +uint32 BotDatabase::GetGroupIDByMemberID(uint32 member_id, std::string& error_message) +{ + if (!member_id) + return 0; + + std::string query = StringFormat("SELECT `groups_index` FROM `bot_group_members` WHERE `bot_id` = '%u'", member_id); + auto results = QueryDatabase(query); + if (!results.Success() || !results.RowCount()) { + error_message = results.ErrorMessage(); + return 0; + } + auto row = results.begin(); + + return atoi(row[0]); +} + +bool BotDatabase::CreateBotGroup(std::string& group_name, uint32 leader_id, std::string& error_message) +{ + if (group_name.empty() || !leader_id) + return false; + + if (DoesBotGroupExist(group_name)) + return false; + + std::string query = StringFormat("INSERT INTO `bot_groups` (`group_leader_id`, `group_name`) VALUES ('%u', '%s')", leader_id, group_name.c_str()); + auto results = QueryDatabase(query); + if (!results.Success()) { + error_message = results.ErrorMessage(); + return false; + } + + auto group_id = results.LastInsertedID(); + if (!group_id) + return false; + + query = StringFormat("INSERT INTO `bot_group_members` (`groups_index`, `bot_id`) VALUES ('%u', '%u')", group_id, leader_id); + results = QueryDatabase(query); + if (!results.Success()) { + error_message = results.ErrorMessage(); + return false; + } + + return true; +} + +bool BotDatabase::DeleteBotGroup(uint32 leader_id, std::string& error_message) +{ + if (!leader_id) + return false; + + uint32 group_id = GetGroupIDByLeaderID(leader_id, error_message); + if (!group_id || !error_message.empty()) + return false; + + std::string query = StringFormat("DELETE FROM `bot_group_members` WHERE `groups_index` = '%u'", group_id); + auto results = QueryDatabase(query); + if (!results.Success()) { + error_message = results.ErrorMessage(); + return false; + } + + query = StringFormat("DELETE FROM `bot_groups` WHERE `groups_index` = '%u'", group_id); + results = QueryDatabase(query); + if (!results.Success()) { + error_message = results.ErrorMessage(); + return false; + } + + return true; +} + +bool BotDatabase::AddMemberToBotGroup(uint32 leader_id, uint32 member_id, std::string& error_message) +{ + if (!leader_id || !member_id) + return false; + + uint32 group_id = GetGroupIDByLeaderID(leader_id, error_message); + if (!group_id || !error_message.empty()) + return false; + + std::string query = StringFormat("INSERT INTO `bot_group_members` (`groups_index`, `bot_id`) VALUES ('%u', '%u')", group_id, member_id); + auto results = QueryDatabase(query); + if (!results.Success()) { + error_message = results.ErrorMessage(); + return false; + } + + return true; +} + +bool BotDatabase::RemoveMemberFromBotGroup(uint32 member_id, std::string& error_message) +{ + if (!member_id) + return false; + + if (GetGroupIDByLeaderID(member_id, error_message)) + return DeleteBotGroup(member_id, error_message); + + if (!error_message.empty()) + return false; + + std::string query = StringFormat("DELETE FROM `bot_group_members` WHERE `bot_id` = '%u'", member_id); + auto results = QueryDatabase(query); + if (!results.Success()) { + error_message = results.ErrorMessage(); + return false; + } + + return true; +} + +uint32 BotDatabase::GetGroupIDForLoadGroup(uint32 owner_id, std::string& group_name, std::string& error_message) +{ + if (!owner_id || group_name.empty()) + return 0; + + std::string query = StringFormat("SELECT `groups_index`, `group_name` FROM `vw_bot_groups` WHERE `owner_id` = '%u'", owner_id); + auto results = QueryDatabase(query); + if (!results.Success() || !results.RowCount()) { + error_message = results.ErrorMessage(); + return 0; + } + + for (auto row = results.begin(); row != results.end(); ++row) { + if (!group_name.compare(row[1])) + return atoi(row[0]); + } + + return 0; +} + +std::map> BotDatabase::LoadGroup(std::string& group_name, std::string& error_message) +{ + std::map> group_list; + if (group_name.empty()) + return group_list; + + uint32 group_id = GetGroupIDByGroupName(group_name, error_message); + if (!group_id || !error_message.empty()) + return group_list; + + std::string query = StringFormat("SELECT `bot_id` FROM `bot_group_members` WHERE `groups_index` = '%u'", group_id); + auto results = QueryDatabase(query); + if (!results.Success()) { + error_message = results.ErrorMessage(); + return group_list; + } + + for (auto row = results.begin(); row != results.end(); ++row) + group_list[group_id].push_back(atoi(row[0])); + + return group_list; +} + +std::list> BotDatabase::GetGroupsListByOwnerID(uint32 owner_id, std::string& error_message) +{ + std::list> groups_list; + if (!owner_id) + return groups_list; + + std::string query = StringFormat("SELECT `group_name`, `group_leader_name` FROM `vw_bot_groups` WHERE `owner_id` = '%u'", owner_id); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + error_message = results.ErrorMessage(); + return groups_list; + } + + for (auto row = results.begin(); row != results.end(); ++row) + groups_list.push_back(std::pair(row[0], row[1])); + + return groups_list; +} diff --git a/zone/bot_database.h b/zone/bot_database.h new file mode 100644 index 000000000..e98388716 --- /dev/null +++ b/zone/bot_database.h @@ -0,0 +1,87 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef BOT_DATABASE_H +#define BOT_DATABASE_H + +#include "../common/dbcore.h" +#include "../common/eq_packet_structs.h" + +#include +#include +#include + +class BotDatabase : public DBcore +{ + +public: + BotDatabase(); + BotDatabase(const char* host, const char* user, const char* passwd, const char* database, uint32 port); + virtual ~BotDatabase(); + + bool Connect(const char* host, const char* user, const char* passwd, const char* database, uint32 port); + + bool GetCommandSettings(std::map>> &bot_command_settings); + + // Bot command functions + bool GetInspectMessage(uint32 bot_id, InspectMessage_Struct* message); + bool SetInspectMessage(uint32 bot_id, const InspectMessage_Struct* message); + bool SetAllInspectMessages(uint32 owner_id, const InspectMessage_Struct* message); + + bool SetAllArmorColorBySlot(uint32 owner_id, int16 slot_id, uint32 rgb_value); + bool SetAllArmorColors(uint32 owner_id, uint32 rgb_value); + + bool SetHelmAppearance(uint32 owner_id, uint32 bot_id, bool show_flag = true); + bool SetAllHelmAppearances(uint32 owner_id, bool show_flag = true); + + bool ToggleHelmAppearance(uint32 owner_id, uint32 bot_id); + bool ToggleAllHelmAppearances(uint32 owner_id); + + bool SetFollowDistance(uint32 owner_id, uint32 bot_id, uint32 follow_distance); + bool SetAllFollowDistances(uint32 owner_id, uint32 follow_distance); + + uint32 Clone(uint32 owner_id, uint32 bot_id, const char* clone_name); + bool CloneInventory(uint32 owner_id, uint32 bot_id, uint32 clone_id); + + // Bot-group functions + bool DoesBotGroupExist(std::string& group_name); + + uint32 GetGroupIDByGroupName(std::string& group_name, std::string& error_message); + uint32 GetLeaderIDByGroupName(std::string& group_name, std::string& error_message); + std::string GetGroupNameByGroupID(uint32 group_id, std::string& error_message); + std::string GetGroupNameByLeaderID(uint32 leader_id, std::string& error_message); + uint32 GetGroupIDByLeaderID(uint32 leader_id, std::string& error_message); + uint32 GetLeaderIDByGroupID(uint32 group_id, std::string& error_message); + + uint32 GetGroupIDByMemberID(uint32 member_id, std::string& error_message); + + bool CreateBotGroup(std::string& group_name, uint32 leader_id, std::string& error_message); + bool DeleteBotGroup(uint32 leader_id, std::string& error_message); + bool AddMemberToBotGroup(uint32 leader_id, uint32 member_id, std::string& error_message); + bool RemoveMemberFromBotGroup(uint32 member_id, std::string& error_message); + + uint32 GetGroupIDForLoadGroup(uint32 owner_id, std::string& group_name, std::string& error_message); + std::map> LoadGroup(std::string& group_name, std::string& error_message); + + std::list> GetGroupsListByOwnerID(uint32 owner_id, std::string& error_message); +}; + +extern BotDatabase botdb; + +#endif diff --git a/zone/bot_structs.h b/zone/bot_structs.h index 09140ae60..c8b9623eb 100644 --- a/zone/bot_structs.h +++ b/zone/bot_structs.h @@ -1,3 +1,21 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + #ifndef BOT_STRUCTS #define BOT_STRUCTS @@ -13,6 +31,7 @@ struct BotsAvailableList { uint16 BotClass; uint8 BotLevel; uint16 BotRace; + uint8 BotGender; }; struct BotGroup { diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 66afc345d..46d10c36f 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -1,3 +1,21 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + #ifdef BOTS #include "bot.h" @@ -1025,7 +1043,7 @@ bool Bot::AI_IdleCastCheck() { // bard bots if(!AICastSpell(this, 100, SpellType_Cure)) { if(!AICastSpell(this, 100, SpellType_Heal)) { - if((!RuleB(Bots, BotBardUseOutOfCombatSongs) || !GetBardUseOutOfCombatSongs()) || !AICastSpell(this, 100, SpellType_Buff)) { // skips if rule is false + if((!RuleB(Bots, BotBardUseOutOfCombatSongs) || !GetAltOutOfCombatBehavior()) || !AICastSpell(this, 100, SpellType_Buff)) { // skips if rule is false if(!AICastSpell(this, 100, SpellType_InCombatBuff)) { // this tries to keep some combat buffs on the group until engaged code can pick up the buffing // } @@ -1261,7 +1279,7 @@ bool Bot::AIHealRotation(Mob* tar, bool useFastHeals) { return false; } - if(!AI_HasSpells()) + if (!AI_HasSpells()) return false; if(tar->GetAppearance() == eaDead) { @@ -1303,11 +1321,11 @@ bool Bot::AIHealRotation(Mob* tar, bool useFastHeals) { } // If there is still no spell id, then there isn't going to be one so we are done - if(botSpell.SpellId == 0) + if (botSpell.SpellId == 0) return false; // Can we cast this spell on this target? - if(!(spells[botSpell.SpellId].targettype==ST_GroupTeleport || spells[botSpell.SpellId].targettype == ST_Target || tar == this) + if (!(spells[botSpell.SpellId].targettype == ST_GroupTeleport || spells[botSpell.SpellId].targettype == ST_Target || tar == this) && !(tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) return false; diff --git a/zone/client.cpp b/zone/client.cpp index f41e35bd2..6836b2a02 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2003 EQEMu Development Team (http://eqemulator.org) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -44,6 +44,9 @@ extern volatile bool RunLoops; #include "zonedb.h" #include "petitions.h" #include "command.h" +#ifdef BOTS +#include "bot_command.h" +#endif #include "string_ids.h" #include "guild_mgr.h" @@ -1046,6 +1049,24 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s break; } +#ifdef BOTS + if (message[0] == BOT_COMMAND_CHAR) { + if (bot_command_dispatch(this, message) == -2) { + if (parse->PlayerHasQuestSub(EVENT_COMMAND)) { + int i = parse->EventPlayer(EVENT_COMMAND, this, message, 0); + if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) { + Message(13, "Bot command '%s' not recognized.", message); + } + } + else { + if (!RuleB(Chat, SuppressCommandErrors)) + Message(13, "Bot command '%s' not recognized.", message); + } + } + break; + } +#endif + Mob* sender = this; if (GetPet() && GetPet()->FindType(SE_VoiceGraft)) sender = GetPet(); @@ -3561,7 +3582,7 @@ void Client::Insight(uint32 t_id) } strcat(resists," to disease."); - Message(0,"Your target is a level %i %s. It appears %s and %s for its level. It seems %s",who->GetLevel(),GetEQClassName(who->GetClass(),1),dmg,hitpoints,resists); + Message(0,"Your target is a level %i %s. It appears %s and %s for its level. It seems %s",who->GetLevel(),GetClassIDName(who->GetClass(),1),dmg,hitpoints,resists); } void Client::GetGroupAAs(GroupLeadershipAA_Struct *into) const { @@ -7546,9 +7567,14 @@ void Client::GarbleMessage(char *message, uint8 variance) int delimiter_count = 0; // Don't garble # commands - if (message[0] == '#') + if (message[0] == COMMAND_CHAR) return; +#ifdef BOTS + if (message[0] == BOT_COMMAND_CHAR) + return; +#endif + for (size_t i = 0; i < strlen(message); i++) { // Client expects hex values inside of a text link body if (message[i] == delimiter) { diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index 9fc36a57e..adf477538 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -1,5 +1,5 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2003 EQEMu Development Team (http://eqemulator.net) +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -160,7 +160,7 @@ int32 Client::LevelRegen() bool sitting = IsSitting(); bool feigned = GetFeigned(); int level = GetLevel(); - bool bonus = GetRaceBitmask(GetBaseRace()) & RuleI(Character, BaseHPRegenBonusRaces); + bool bonus = GetPlayerRaceBit(GetBaseRace()) & RuleI(Character, BaseHPRegenBonusRaces); uint8 multiplier1 = bonus ? 2 : 1; int32 hp = 0; //these calculations should match up with the info from Monkly Business, which was last updated ~05/2008: http://www.monkly-business.net/index.php?pageid=abilities diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 7aa4c0de5..fb7216b86 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2009 EQEMu Development Team (http://eqemulator.net) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -3896,7 +3896,6 @@ void Client::Handle_OP_Camp(const EQApplicationPacket *app) { #ifdef BOTS // This block is necessary to clean up any bot objects owned by a Client - Bot::BotHealRotationsClear(this); Bot::BotOrderCampAll(this); #endif if (IsLFP()) diff --git a/zone/command.cpp b/zone/command.cpp index b5d060f87..dbbed07db 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2002 EQEMu Development Team (http://eqemulator.org) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -86,7 +86,7 @@ void command_pf(Client *c, const Seperator *message); std::map commandlist; std::map commandaliases; -//All allocated CommandRecords get put in here so they get deleted on shutdown +// All allocated CommandRecords get put in here so they get deleted on shutdown LinkedList cleanup_commandlist; /* @@ -103,9 +103,9 @@ int command_notavail(Client *c, const char *message) return -1; } -/*****************************************************************************/ -/* the rest below here could be in a dynamically loaded module eventually */ -/*****************************************************************************/ +/************************************************************************** +/* the rest below here could be in a dynamically loaded module eventually * +/*************************************************************************/ /* @@ -163,7 +163,7 @@ int command_init(void) command_add("bind", "- Sets your targets bind spot to their current location", 200, command_bind) || #ifdef BOTS - command_add("bot", "- Type \"#bot help\" to the see the list of available commands for bots.", 0, command_bot) || + command_add("bot", "- Type \"#bot help\" or \"^help\" to the see the list of available commands for bots.", 0, command_bot) || #endif command_add("camerashake", "Shakes the camera on everyone's screen globally.", 80, command_camerashake) || @@ -9665,14 +9665,6 @@ void command_showspellslist(Client *c, const Seperator *sep) return; } -// All new code added to command.cpp ought to be BEFORE this comment line. Do no append code to this file below the BOTS code block. -#ifdef BOTS -// Function delegate to support the command interface for Bots with the client. -void command_bot(Client *c, const Seperator *sep) { - Bot::ProcessBotCommands(c, sep); -} -#endif - void command_raidloot(Client *c, const Seperator *sep) { if(!sep->arg[1][0]) { @@ -10842,4 +10834,30 @@ void command_reloadperlexportsettings(Client *c, const Seperator *sep) safe_delete(pack); } -} \ No newline at end of file +} + + +// All new code added to command.cpp should be BEFORE this comment line. Do no append code to this file below the BOTS code block. +#ifdef BOTS +#include "bot_command.h" +// Function delegate to support the command interface for Bots with the client. +void command_bot(Client *c, const Seperator *sep) +{ + std::string bot_message = sep->msg; + bot_message = bot_message.substr(bot_message.find_first_not_of("#bot")); + bot_message[0] = BOT_COMMAND_CHAR; + + if (bot_command_dispatch(c, bot_message.c_str()) == -2) { + if (parse->PlayerHasQuestSub(EVENT_COMMAND)) { + int i = parse->EventPlayer(EVENT_COMMAND, c, bot_message, 0); + if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) { + c->Message(13, "Bot command '%s' not recognized.", bot_message.c_str()); + } + } + else { + if (!RuleB(Chat, SuppressCommandErrors)) + c->Message(13, "Bot command '%s' not recognized.", bot_message.c_str()); + } + } +} +#endif diff --git a/zone/command.h b/zone/command.h index eb3633f57..987adf674 100644 --- a/zone/command.h +++ b/zone/command.h @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/zone/embparser.cpp b/zone/embparser.cpp index 12501b089..037649366 100644 --- a/zone/embparser.cpp +++ b/zone/embparser.cpp @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2006 EQEMu Development Team (http://eqemulator.net) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -9,11 +9,11 @@ but WITHOUT ANY WARRANTY except by those people which sell it, which are required to give you total support for your newly bought product; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef EMBPERL @@ -1052,8 +1052,8 @@ void PerlembParser::ExportMobVariables(bool isPlayerQuest, bool isGlobalPlayerQu if(mob) { ExportVar(package_name.c_str(), "name", mob->GetName()); - ExportVar(package_name.c_str(), "race", GetRaceName(mob->GetRace())); - ExportVar(package_name.c_str(), "class", GetEQClassName(mob->GetClass())); + ExportVar(package_name.c_str(), "race", GetRaceIDName(mob->GetRace())); + ExportVar(package_name.c_str(), "class", GetClassIDName(mob->GetClass())); ExportVar(package_name.c_str(), "ulevel", mob->GetLevel()); ExportVar(package_name.c_str(), "userid", mob->GetID()); } diff --git a/zone/embxs.cpp b/zone/embxs.cpp index 664416587..8a1bde544 100644 --- a/zone/embxs.cpp +++ b/zone/embxs.cpp @@ -1,26 +1,30 @@ -/* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2003 EQEMu Development Team (http://eqemulator.net) +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY except by those people which sell it, which + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which are required to give you total support for your newly bought product; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + #ifdef EMBPERL #include "../common/global_define.h" #include "../common/eqemu_logsys.h" #include "masterentity.h" #include "command.h" +#ifdef BOTS +#include "bot_command.h" +#endif #include "embperl.h" #include "embxs.h" diff --git a/zone/entity.h b/zone/entity.h index df0b327a5..986a60d99 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2003 EQEMu Development Team (http://eqemulator.net) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -466,7 +466,6 @@ private: #ifdef BOTS public: void AddBot(Bot* newBot, bool SendSpawnPacket = true, bool dontqueue = false); - void BotPickLock(Bot* rogue); bool RemoveBot(uint16 entityID); Mob* GetMobByBotID(uint32 botID); Bot* GetBotByBotID(uint32 botID); diff --git a/zone/groups.cpp b/zone/groups.cpp index d65bafcfe..353751b5b 100644 --- a/zone/groups.cpp +++ b/zone/groups.cpp @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemu.org) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,6 +15,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + #include "../common/global_define.h" #include "../common/eqemu_logsys.h" #include "masterentity.h" @@ -938,6 +939,41 @@ void Group::DisbandGroup() { safe_delete(outapp); } +void Group::GetMemberList(std::list& member_list, bool clear_list) +{ + if (clear_list) + member_list.clear(); + + for (auto member_iter : members) { + if (member_iter) + member_list.push_back(member_iter); + } +} + +void Group::GetClientList(std::list& client_list, bool clear_list) +{ + if (clear_list) + client_list.clear(); + + for (auto client_iter : members) { + if (client_iter && client_iter->IsClient()) + client_list.push_back(client_iter->CastToClient()); + } +} + +#ifdef BOTS +void Group::GetBotList(std::list& bot_list, bool clear_list) +{ + if (clear_list) + bot_list.clear(); + + for (auto bot_iter : members) { + if (bot_iter && bot_iter->IsBot()) + bot_list.push_back(bot_iter->CastToBot()); + } +} +#endif + bool Group::Process() { if(disbandcheck && !GroupCount()) return false; @@ -2294,6 +2330,30 @@ void Group::SetPuller(const char *NewPullerName) } } +bool Group::AmIMainTank(const char *mob_name) +{ + if (!mob_name) + return false; + + return !((bool)MainTankName.compare(mob_name)); +} + +bool Group::AmIMainAssist(const char *mob_name) +{ + if (!mob_name) + return false; + + return !((bool)MainTankName.compare(mob_name)); +} + +bool Group::AmIPuller(const char *mob_name) +{ + if (!mob_name) + return false; + + return !((bool)PullerName.compare(mob_name)); +} + bool Group::HasRole(Mob *m, uint8 Role) { if(!m) diff --git a/zone/groups.h b/zone/groups.h index 7dcbd848b..3da2aae54 100644 --- a/zone/groups.h +++ b/zone/groups.h @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemu.org) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -59,6 +59,11 @@ public: bool DelMemberOOZ(const char *Name); bool DelMember(Mob* oldmember,bool ignoresender = false); void DisbandGroup(); + void GetMemberList(std::list& member_list, bool clear_list = true); + void GetClientList(std::list& client_list, bool clear_list = true); +#ifdef BOTS + void GetBotList(std::list& bot_list, bool clear_list = true); +#endif bool IsGroupMember(Mob* client); bool IsGroupMember(const char *Name); bool Process(); @@ -123,6 +128,9 @@ public: const char *GetMainTankName() { return MainTankName.c_str(); } const char *GetMainAssistName() { return MainAssistName.c_str(); } const char *GetPullerName() { return PullerName.c_str(); } + bool AmIMainTank(const char *mob_name); + bool AmIMainAssist(const char *mob_name); + bool AmIPuller(const char *mob_name); void SetNPCMarker(const char *NewNPCMarkerName); void UnMarkNPC(uint16 ID); void SendMarkedNPCsToMember(Client *c, bool Clear = false); diff --git a/zone/heal_rotation.cpp b/zone/heal_rotation.cpp new file mode 100644 index 000000000..465f66f00 --- /dev/null +++ b/zone/heal_rotation.cpp @@ -0,0 +1,882 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "bot.h" + +#define SAFE_HP_RATIO_CLOTH 95.0f +#define SAFE_HP_RATIO_LEATHER 90.0f +#define SAFE_HP_RATIO_CHAIN 80.0f +#define SAFE_HP_RATIO_PLATE 75.0f + +#define CRITICAL_HP_RATIO_CLOTH 30.0f +#define CRITICAL_HP_RATIO_LEATHER 25.0f +#define CRITICAL_HP_RATIO_CHAIN 15.0f +#define CRITICAL_HP_RATIO_PLATE 10.0f + +HealRotation::HealRotation(Bot* hr_creator, uint32 interval_ms, bool fast_heals, bool adaptive_targeting, bool casting_override) +{ + m_member_pool.push_back(hr_creator); + + m_creation_time_ms = Timer::GetCurrentTime(); + m_last_heal_time_ms = m_creation_time_ms; + m_interval_ms = ((interval_ms >= CASTING_CYCLE_MINIMUM_INTERVAL) ? (interval_ms) : (CASTING_CYCLE_MINIMUM_INTERVAL)); + m_next_cast_time_ms = m_creation_time_ms; + m_next_poke_time_ms = m_creation_time_ms; + m_healing_stats_begin_ms = m_creation_time_ms; + m_fast_heals = fast_heals; + m_adaptive_targeting = adaptive_targeting; + m_casting_override = casting_override; + m_casting_target_poke = true; + m_active_heal_target = false; + + ResetArmorTypeHPLimits(); + + m_is_active = false; +} + +void HealRotation::SetIntervalMS(uint32 interval_ms) +{ + if (interval_ms > CASTING_CYCLE_MAXIMUM_INTERVAL) + interval_ms = CASTING_CYCLE_MAXIMUM_INTERVAL; + else if (interval_ms < CASTING_CYCLE_MINIMUM_INTERVAL) + interval_ms = CASTING_CYCLE_MINIMUM_INTERVAL; + + m_interval_ms = interval_ms; +} + +void HealRotation::SetIntervalS(uint32 interval_s) +{ + interval_s *= 1000; + if (interval_s > CASTING_CYCLE_MAXIMUM_INTERVAL) + interval_s = CASTING_CYCLE_MAXIMUM_INTERVAL; + else if (interval_s < CASTING_CYCLE_MINIMUM_INTERVAL) + interval_s = CASTING_CYCLE_MINIMUM_INTERVAL; + + m_interval_ms = interval_s; +} + +bool HealRotation::AddMemberToPool(Bot* hr_member) +{ + if (!hr_member) + return false; + if (!IsMemberClass(hr_member->GetClass())) + return false; + if (m_member_pool.size() >= RuleI(Bots, HealRotationMaxMembers)) + return false; + + for (auto find_iter : m_member_pool) { + if (find_iter == hr_member) + return false; + } + + m_member_pool.push_back(hr_member); + valid_state(); + + return true; +} + +bool HealRotation::AddTargetToPool(Mob* hr_target) +{ + if (!hr_target) + return false; + if (!valid_state()) + return false; + if (!IsTargetMobType(hr_target)) + return false; + if (m_target_pool.size() >= RuleI(Bots, HealRotationMaxTargets)) + return false; + + for (auto find_iter : m_target_pool) { + if (find_iter == hr_target) + return false; + } + + m_target_pool.push_back(hr_target); + + return true; +} + +bool HealRotation::RemoveMemberFromPool(Bot* hr_member) +{ + if (!hr_member) + return true; + + for (auto member_iter : m_member_pool) { + if (member_iter != hr_member) + continue; + + m_member_is_casting.erase(hr_member); + m_member_pool.remove(hr_member); + valid_state(); + return true; + } + return false; +} + +bool HealRotation::RemoveTargetFromPool(Mob* hr_target) +{ + if (!hr_target) + return true; + if (!valid_state()) + return true; + + for (auto target_iter : m_target_pool) { + if (target_iter != hr_target) + continue; + + m_target_healing_stats_2.erase(hr_target); + m_target_healing_stats_1.erase(hr_target); + m_target_pool.remove(hr_target); + m_casting_target_poke = false; + bias_targets(); + return true; + } + + return false; +} + +bool HealRotation::ClearMemberPool() +{ + m_is_active = false; + m_cycle_pool.clear(); + m_casting_target_poke = false; + m_active_heal_target = false; + + ClearTargetPool(); + + auto clear_list = m_member_pool; + for (auto member_iter : clear_list) + member_iter->LeaveHealRotationMemberPool(); + + return true; +} + +bool HealRotation::ClearTargetPool() +{ + m_is_active = false; + + auto clear_list = m_target_pool; + for (auto target_iter : clear_list) + target_iter->LeaveHealRotationTargetPool(); + + m_casting_target_poke = false; + bias_targets(); + + return m_target_pool.empty(); +} + +bool HealRotation::Start() +{ + m_is_active = false; + if (m_member_pool.empty() || m_target_pool.empty()) + return false; + + m_cycle_pool = m_member_pool; + m_is_active = true; + + return true; +} + +bool HealRotation::Stop() +{ + m_is_active = false; + m_active_heal_target = false; + m_cycle_pool.clear(); + + return true; +} + +Bot* HealRotation::CastingMember() +{ + if (!m_is_active) + return nullptr; + + if (m_cycle_pool.empty()) { + cycle_refresh(); + + if (m_cycle_pool.empty()) + return nullptr; + } + + return m_cycle_pool.front(); +} + +bool HealRotation::PokeCastingTarget() +{ + if (!m_is_active) + return false; + + uint32 current_time = Timer::GetCurrentTime(); + + if (current_time < m_next_poke_time_ms) { + auto hr_target = CastingTarget(); + if (hr_target && hr_target->DontHealMeBefore() > current_time) + m_next_poke_time_ms = current_time; + else + return m_active_heal_target; + } + + m_next_poke_time_ms = (current_time + POKE_PROPAGATION_DELAY); + + if (m_healing_stats_begin_ms + HEALING_STATS_RESET_INTERVAL <= current_time) + StartNewTargetHealingStatsCycle(current_time); + + m_casting_target_poke = false; + bias_targets(); + + return m_active_heal_target; +} + +Mob* HealRotation::CastingTarget() +{ + if (!m_is_active) + return nullptr; + if (!m_active_heal_target) + return nullptr; + + return m_target_pool.front(); +} + +bool HealRotation::AdvanceRotation(bool use_interval) +{ + m_cycle_pool.pop_front(); + m_next_cast_time_ms = Timer::GetCurrentTime(); + if (use_interval) { + m_next_poke_time_ms = m_next_cast_time_ms; + m_next_cast_time_ms += m_interval_ms; + } + else { + m_next_cast_time_ms += ADVANCE_ROTATION_MINIMUM_INTERVAL; + } + + if (m_cycle_pool.empty()) + cycle_refresh(); + + return (!m_cycle_pool.empty()); +} + +bool HealRotation::IsMemberInPool(Bot* hr_member) +{ + if (!hr_member) + return false; + if (m_member_pool.empty()) + return false; + + for (auto find_iter : m_member_pool) { + if (find_iter == hr_member) + return true; + } + + return false; +} + +bool HealRotation::IsTargetInPool(Mob* hr_target) +{ + if (!hr_target) + return false; + if (m_target_pool.empty()) + return false; + + for (auto find_iter : m_target_pool) { + if (find_iter == hr_target) + return true; + } + + return false; +} + +void HealRotation::SetMemberIsCasting(Bot* hr_member, bool flag) +{ + if (!hr_member) + return; + if (!IsMemberInPool(hr_member)) + return; + + m_member_is_casting[hr_member] = flag; +} + +bool HealRotation::MemberIsCasting(Bot* hr_member) +{ + if (!hr_member) + return false; + if (m_member_is_casting.find(hr_member) == m_member_is_casting.end()) + return false; + + return m_member_is_casting[hr_member]; +} + +void HealRotation::UpdateTargetHealingStats(Mob* hr_target) +{ + if (!hr_target) + return; + if (!IsTargetInPool(hr_target)) + return; + + m_last_heal_time_ms = Timer::GetCurrentTime(); + + m_target_healing_stats_1[hr_target].last_heal_time_ms = m_last_heal_time_ms; + ++m_target_healing_stats_1[hr_target].heal_count; +} + +void HealRotation::StartNewTargetHealingStatsCycle(uint32 current_time) +{ + m_target_healing_stats_2 = m_target_healing_stats_1; + m_target_healing_stats_1.clear(); + + m_healing_stats_begin_ms = current_time; +} + +uint32 HealRotation::HealCount(Mob* hr_target) +{ + if (!hr_target) + return 0; + + uint32 heal_count = 0; + if (m_target_healing_stats_1.find(hr_target) != m_target_healing_stats_1.end()) + heal_count += m_target_healing_stats_1[hr_target].heal_count; + + return heal_count; +} + +uint32 HealRotation::ExtendedHealCount(Mob* hr_target) +{ + if (!hr_target) + return 0; + + uint32 heal_count = 0; + if (m_target_healing_stats_1.find(hr_target) != m_target_healing_stats_1.end()) + heal_count += m_target_healing_stats_1[hr_target].heal_count; + if (m_target_healing_stats_2.find(hr_target) != m_target_healing_stats_2.end()) + heal_count += m_target_healing_stats_2[hr_target].heal_count; + + return heal_count; +} + +float HealRotation::HealFrequency(Mob* hr_target) +{ + if (!hr_target) + return 0.0f; + + float time_base = 0; + uint32 heal_count = 0; + if (m_target_healing_stats_1.find(hr_target) != m_target_healing_stats_1.end()) { + heal_count += m_target_healing_stats_1[hr_target].heal_count; + time_base = (Timer::GetCurrentTime() - m_target_healing_stats_1[hr_target].last_heal_time_ms); + } + + time_base /= 1000; + if (!time_base) + time_base = HEALING_STATS_RESET_INTERVAL_S; + + if (heal_count) + return ((float)1 / (time_base / heal_count)); + else + return ((float)1 / time_base); +} + +float HealRotation::ExtendedHealFrequency(Mob* hr_target) +{ + if (!hr_target) + return 0.0f; + + uint32 current_time = Timer::GetCurrentTime(); + uint32 heal_count = 0; + float time_base = 0; + if (m_target_healing_stats_1.find(hr_target) != m_target_healing_stats_1.end()) { + heal_count += m_target_healing_stats_1[hr_target].heal_count; + time_base = (current_time - m_target_healing_stats_1[hr_target].last_heal_time_ms + HEALING_STATS_RESET_INTERVAL); + } + if (m_target_healing_stats_2.find(hr_target) != m_target_healing_stats_2.end()) { + heal_count += m_target_healing_stats_2[hr_target].heal_count; + time_base = (current_time - m_target_healing_stats_2[hr_target].last_heal_time_ms); + } + + time_base /= 1000; + if (!time_base) + time_base = (HEALING_STATS_RESET_INTERVAL_S * 2); + + if (heal_count) + return ((float)1 / (time_base / heal_count)); + else + return ((float)1 / time_base); +} + +HealingStats* HealRotation::TargetHealingStats1(Mob* hr_target) +{ + if (!hr_target) + return nullptr; + if (m_target_healing_stats_1.find(hr_target) == m_target_healing_stats_1.end()) + return nullptr; + + return &m_target_healing_stats_1[hr_target]; +} + +HealingStats* HealRotation::TargetHealingStats2(Mob* hr_target) +{ + if (!hr_target) + return nullptr; + if (m_target_healing_stats_2.find(hr_target) == m_target_healing_stats_2.end()) + return nullptr; + + return &m_target_healing_stats_2[hr_target]; +} + +bool HealRotation::SetArmorTypeSafeHPRatio(uint8 armor_type, float hp_ratio) +{ + if (armor_type >= ARMOR_TYPE_COUNT) + return false; + if (hp_ratio < CRITICAL_HP_RATIO_BASE || hp_ratio > SAFE_HP_RATIO_BASE) + return false; + if (hp_ratio < m_critical_hp_ratio[armor_type]) + return false; + + m_safe_hp_ratio[armor_type] = hp_ratio; + + return true; +} + +bool HealRotation::SetArmorTypeCriticalHPRatio(uint8 armor_type, float hp_ratio) +{ + if (armor_type >= ARMOR_TYPE_COUNT) + return false; + if (hp_ratio < CRITICAL_HP_RATIO_BASE || hp_ratio > SAFE_HP_RATIO_BASE) + return false; + if (hp_ratio > m_safe_hp_ratio[armor_type]) + return false; + + m_critical_hp_ratio[armor_type] = hp_ratio; + + return true; +} + +float HealRotation::ArmorTypeSafeHPRatio(uint8 armor_type) +{ + if (armor_type < ARMOR_TYPE_COUNT) + return m_safe_hp_ratio[armor_type]; + else + return m_safe_hp_ratio[ARMOR_TYPE_UNKNOWN]; +} + +float HealRotation::ArmorTypeCriticalHPRatio(uint8 armor_type) +{ + if (armor_type < ARMOR_TYPE_COUNT) + return m_critical_hp_ratio[armor_type]; + else + return m_critical_hp_ratio[ARMOR_TYPE_UNKNOWN]; +} + +void HealRotation::ResetArmorTypeHPLimits() +{ + m_safe_hp_ratio[ARMOR_TYPE_UNKNOWN] = SAFE_HP_RATIO_BASE; + m_safe_hp_ratio[ARMOR_TYPE_CLOTH] = SAFE_HP_RATIO_CLOTH; + m_safe_hp_ratio[ARMOR_TYPE_LEATHER] = SAFE_HP_RATIO_LEATHER; + m_safe_hp_ratio[ARMOR_TYPE_CHAIN] = SAFE_HP_RATIO_CHAIN; + m_safe_hp_ratio[ARMOR_TYPE_PLATE] = SAFE_HP_RATIO_PLATE; + + m_critical_hp_ratio[ARMOR_TYPE_UNKNOWN] = CRITICAL_HP_RATIO_BASE; + m_critical_hp_ratio[ARMOR_TYPE_CLOTH] = CRITICAL_HP_RATIO_CLOTH; + m_critical_hp_ratio[ARMOR_TYPE_LEATHER] = CRITICAL_HP_RATIO_LEATHER; + m_critical_hp_ratio[ARMOR_TYPE_CHAIN] = CRITICAL_HP_RATIO_CHAIN; + m_critical_hp_ratio[ARMOR_TYPE_PLATE] = CRITICAL_HP_RATIO_PLATE; +} + +bool HealRotation::valid_state() +{ + m_member_pool.remove(nullptr); + m_member_pool.remove_if([](Mob* l) {return (!IsMemberClass(l->GetClass())); }); + + cycle_refresh(); + + if (m_member_pool.empty()) + ClearTargetPool(); // Consumes HealRotation at this point + + return (!m_member_pool.empty()); +} + +void HealRotation::cycle_refresh() +{ + m_is_active = false; + m_cycle_pool.clear(); + if (m_member_pool.empty()) + return; + + m_cycle_pool = m_member_pool; + + m_is_active = true; +} + +bool HealRotation::healable_target(bool use_class_at, bool critical_only) +{ + if (m_target_pool.empty()) + return false; + + auto healable_target = m_target_pool.front(); + if (!healable_target) + return false; + if (healable_target->DontHealMeBefore() > Timer::GetCurrentTime()) + return false; + if (healable_target->GetAppearance() == eaDead) + return false; + + if (use_class_at) { + if (critical_only && healable_target->GetHPRatio() > m_critical_hp_ratio[ClassArmorType(healable_target->GetClass())]) + return false; + if (healable_target->GetHPRatio() > m_safe_hp_ratio[ClassArmorType(healable_target->GetClass())]) + return false; + if (healable_target->IsBerserk() && (healable_target->GetClass() == WARRIOR || healable_target->GetClass() == BERSERKER)) { + if (healable_target->GetHPRatio() <= RuleI(Combat, BerserkerFrenzyEnd) && healable_target->GetHPRatio() > m_critical_hp_ratio[ClassArmorType(healable_target->GetClass())]) + return false; + } + } + else { + if (critical_only && healable_target->GetHPRatio() > CRITICAL_HP_RATIO_BASE) + return false; + if (healable_target->GetHPRatio() > SAFE_HP_RATIO_BASE) + return false; + if (healable_target->IsBerserk() && (healable_target->GetClass() == WARRIOR || healable_target->GetClass() == BERSERKER)) { + if (healable_target->GetHPRatio() <= RuleI(Combat, BerserkerFrenzyEnd) && healable_target->GetHPRatio() > CRITICAL_HP_RATIO_BASE) + return false; + } + } + + return true; +} + +void HealRotation::bias_targets() +{ +#define LT_HPRATIO(l, r) (l->GetHPRatio() < r->GetHPRatio()) +#define LT_ARMTYPE(l, r) (ClassArmorType(l->GetClass()) < ClassArmorType(r->GetClass())) + +#define EQ_ALIVE(l, r) (l->GetAppearance() != eaDead && r->GetAppearance() != eaDead) +#define EQ_READY(l, r, ct) (l->DontHealMeBefore() <= ct && r->DontHealMeBefore() <= ct) +#define EQ_TANK(l, r) ((l->HasGroup() && l->GetGroup()->AmIMainTank(l->GetCleanName())) && (r->HasGroup() && r->GetGroup()->AmIMainTank(r->GetCleanName()))) +#define EQ_HEALER(l, r) (IsMemberClass(l->GetClass()) && IsMemberClass(r->GetClass())) +#define EQ_ARMTYPE(l, r) (ClassArmorType(l->GetClass()) == ClassArmorType(r->GetClass())) +#define EQ_ATCRIT(l, r) (l->GetHPRatio() <= (*l->TargetOfHealRotation())->ArmorTypeCriticalHPRatio(ClassArmorType(l->GetClass())) && \ + r->GetHPRatio() <= (*r->TargetOfHealRotation())->ArmorTypeCriticalHPRatio(ClassArmorType(r->GetClass()))) +#define EQ_ATWOUND(l, r) (l->GetHPRatio() <= (*l->TargetOfHealRotation())->ArmorTypeSafeHPRatio(ClassArmorType(l->GetClass())) && \ + r->GetHPRatio() <= (*r->TargetOfHealRotation())->ArmorTypeSafeHPRatio(ClassArmorType(r->GetClass()))) + +#define GT_ALIVE(l, r) (l->GetAppearance() != eaDead && r->GetAppearance() == eaDead) +#define GT_READY(l, r, ct) (l->DontHealMeBefore() <= ct && r->DontHealMeBefore() > ct) +#define GT_TANK(l, r) ((l->HasGroup() && l->GetGroup()->AmIMainTank(l->GetCleanName())) && (!r->HasGroup() || !r->GetGroup()->AmIMainTank(r->GetCleanName()))) +#define GT_HEALER(l, r) (IsMemberClass(l->GetClass()) && !IsMemberClass(r->GetClass())) +#define GT_HEALFREQ(l, r) (l->HealRotationHealFrequency() > r->HealRotationHealFrequency()) +#define GT_HEALCNT(l, r) (l->HealRotationHealCount() > r->HealRotationHealCount()) +#define GT_ATCRIT(l, r) (l->GetHPRatio() <= (*l->TargetOfHealRotation())->ArmorTypeCriticalHPRatio(ClassArmorType(l->GetClass())) && \ + r->GetHPRatio() > (*r->TargetOfHealRotation())->ArmorTypeCriticalHPRatio(ClassArmorType(r->GetClass()))) +#define GT_XHEALFREQ(l, r) (l->HealRotationExtendedHealFrequency() > r->HealRotationExtendedHealFrequency()) +#define GT_XHEALCNT(l, r) (l->HealRotationExtendedHealCount() > r->HealRotationExtendedHealCount()) +#define GT_ATWOUND(l, r) (l->GetHPRatio() <= (*l->TargetOfHealRotation())->ArmorTypeSafeHPRatio(ClassArmorType(l->GetClass())) && \ + r->GetHPRatio() > (*r->TargetOfHealRotation())->ArmorTypeSafeHPRatio(ClassArmorType(r->GetClass()))) + + if (m_target_pool.empty()) { + m_casting_target_poke = true; + m_active_heal_target = false; + return; + } + + // attempt to clear invalid target pool entries + m_target_pool.remove(nullptr); + m_target_pool.remove_if([](Mob* l) { return (!IsTargetMobType(l)); }); + + uint32 sort_type = 0; // debug + + while (m_target_pool.size() > 1 && !m_casting_target_poke && !m_adaptive_targeting) { // standard behavior + sort_type = 1; + m_target_pool.sort([](Mob* l, Mob* r) { + if (GT_ALIVE(l, r)) + return true; + uint32 current_time = Timer::GetCurrentTime(); + if (EQ_ALIVE(l, r) && GT_READY(l, r, current_time)) + return true; + if (EQ_ALIVE(l, r) && EQ_READY(l, r, current_time) && GT_TANK(l, r)) + return true; + if (EQ_ALIVE(l, r) && EQ_READY(l, r, current_time) && EQ_TANK(l, r) && LT_HPRATIO(l, r)) + return true; + + return false; + }); + if (m_target_pool.front()->HasGroup() && m_target_pool.front()->GetGroup()->AmIMainTank(m_target_pool.front()->GetCleanName()) && healable_target(false)) + break; + + sort_type = 2; + m_target_pool.sort([](Mob* l, Mob* r) { + if (GT_ALIVE(l, r)) + return true; + uint32 current_time = Timer::GetCurrentTime(); + if (EQ_ALIVE(l, r) && GT_READY(l, r, current_time)) + return true; + if (EQ_ALIVE(l, r) && EQ_READY(l, r, current_time) && GT_HEALER(l, r)) + return true; + if (EQ_ALIVE(l, r) && EQ_READY(l, r, current_time) && EQ_HEALER(l, r) && LT_HPRATIO(l, r)) + return true; + + return false; + }); + if (IsMemberClass(m_target_pool.front()->GetClass()) && healable_target(false)) + break; + + sort_type = 3; // default + m_target_pool.sort([](const Mob* l, const Mob* r) { + if (GT_ALIVE(l, r)) + return true; + uint32 current_time = Timer::GetCurrentTime(); + if (EQ_ALIVE(l, r) && GT_READY(l, r, current_time)) + return true; + if (EQ_ALIVE(l, r) && EQ_READY(l, r, current_time) && LT_HPRATIO(l, r)) + return true; + + return false; + }); + + break; + } + + while (m_target_pool.size() > 1 && !m_casting_target_poke && m_adaptive_targeting) { // adaptive targeting behavior + sort_type = 101; + m_target_pool.sort([](Mob* l, Mob* r) { + if (GT_ALIVE(l, r)) + return true; + uint32 current_time = Timer::GetCurrentTime(); + if (EQ_ALIVE(l, r) && GT_READY(l, r, current_time)) + return true; + if (EQ_ALIVE(l, r) && EQ_READY(l, r, current_time) && GT_HEALFREQ(l, r)) + return true; + + return false; + }); + if (healable_target(true, true)) + break; + + sort_type = 102; + m_target_pool.sort([](Mob* l, Mob* r) { + if (GT_ALIVE(l, r)) + return true; + uint32 current_time = Timer::GetCurrentTime(); + if (EQ_ALIVE(l, r) && GT_READY(l, r, current_time)) + return true; + if (EQ_ALIVE(l, r) && EQ_READY(l, r, current_time) && GT_HEALCNT(l, r)) + return true; + + return false; + }); + if (healable_target(true, true)) + break; + + sort_type = 103; + m_target_pool.sort([](Mob* l, Mob* r) { + if (GT_ALIVE(l, r)) + return true; + uint32 current_time = Timer::GetCurrentTime(); + if (EQ_ALIVE(l, r) && GT_READY(l, r, current_time)) + return true; + if (EQ_ALIVE(l, r) && EQ_READY(l, r, current_time) && GT_TANK(l, r)) + return true; + if (EQ_ALIVE(l, r) && EQ_READY(l, r, current_time) && EQ_TANK(l, r) && LT_HPRATIO(l, r)) + return true; + + return false; + }); + if (m_target_pool.front()->HasGroup() && m_target_pool.front()->GetGroup()->AmIMainTank(m_target_pool.front()->GetCleanName()) && healable_target(true, true)) + break; + + sort_type = 104; + m_target_pool.sort([](const Mob* l, const Mob* r) { + if (GT_ALIVE(l, r)) + return true; + uint32 current_time = Timer::GetCurrentTime(); + if (EQ_ALIVE(l, r) && GT_READY(l, r, current_time)) + return true; + if (EQ_ALIVE(l, r) && EQ_READY(l, r, current_time) && GT_HEALER(l, r)) + return true; + if (EQ_ALIVE(l, r) && EQ_READY(l, r, current_time) && EQ_HEALER(l, r) && LT_HPRATIO(l, r)) + return true; + + return false; + }); + if (IsMemberClass(m_target_pool.front()->GetClass()) && healable_target(true, true)) + break; + + sort_type = 105; + m_target_pool.sort([](const Mob* l, const Mob* r) { + if (GT_ALIVE(l, r)) + return true; + uint32 current_time = Timer::GetCurrentTime(); + if (EQ_ALIVE(l, r) && GT_READY(l, r, current_time)) + return true; + if (EQ_ALIVE(l, r) && EQ_READY(l, r, current_time) && GT_ATCRIT(l, r)) + return true; + if (EQ_ALIVE(l, r) && EQ_READY(l, r, current_time) && EQ_ATCRIT(l, r) && LT_ARMTYPE(l, r)) + return true; + if (EQ_ALIVE(l, r) && EQ_READY(l, r, current_time) && EQ_ATCRIT(l, r) && EQ_ARMTYPE(l, r) && LT_HPRATIO(l, r)) + return true; + + return false; + }); + if (healable_target(true, true)) + break; + + sort_type = 106; + m_target_pool.sort([](Mob* l, Mob* r) { + if (GT_ALIVE(l, r)) + return true; + uint32 current_time = Timer::GetCurrentTime(); + if (EQ_ALIVE(l, r) && GT_READY(l, r, current_time)) + return true; + if (EQ_ALIVE(l, r) && EQ_READY(l, r, current_time) && GT_XHEALFREQ(l, r)) + return true; + + return false; + }); + if (healable_target(true)) + break; + + sort_type = 107; + m_target_pool.sort([](Mob* l, Mob* r) { + if (GT_ALIVE(l, r)) + return true; + uint32 current_time = Timer::GetCurrentTime(); + if (EQ_ALIVE(l, r) && GT_READY(l, r, current_time)) + return true; + if (EQ_ALIVE(l, r) && EQ_READY(l, r, current_time) && GT_XHEALCNT(l, r)) + return true; + + return false; + }); + if (healable_target(true)) + break; + + sort_type = 108; + m_target_pool.sort([](const Mob* l, const Mob* r) { + if (GT_ALIVE(l, r)) + return true; + uint32 current_time = Timer::GetCurrentTime(); + if (EQ_ALIVE(l, r) && GT_READY(l, r, current_time)) + return true; + if (EQ_ALIVE(l, r) && EQ_READY(l, r, current_time) && GT_ATWOUND(l, r)) + return true; + if (EQ_ALIVE(l, r) && EQ_READY(l, r, current_time) && EQ_ATWOUND(l, r) && LT_ARMTYPE(l, r)) + return true; + if (EQ_ALIVE(l, r) && EQ_READY(l, r, current_time) && EQ_ATWOUND(l, r) && EQ_ARMTYPE(l, r) && LT_HPRATIO(l, r)) + return true; + + return false; + }); + if (healable_target()) + break; + + sort_type = 109; // default + m_target_pool.sort([](const Mob* l, const Mob* r) { + if (GT_ALIVE(l, r)) + return true; + uint32 current_time = Timer::GetCurrentTime(); + if (EQ_ALIVE(l, r) && GT_READY(l, r, current_time)) + return true; + if (EQ_ALIVE(l, r) && EQ_READY(l, r, current_time) && LT_HPRATIO(l, r)) + return true; + + return false; + }); + + break; + } + + m_active_heal_target = healable_target(false); + if (!m_active_heal_target) + m_active_heal_target = healable_target(); + + m_casting_target_poke = true; + +#if (EQDEBUG >= 12) + Log.Out(Logs::General, Logs::Error, "HealRotation::bias_targets() - *** Post-processing state ***"); + Log.Out(Logs::General, Logs::Error, "HealRotation Settings:"); + Log.Out(Logs::General, Logs::Error, "HealRotation::m_interval_ms = %u", m_interval_ms); + Log.Out(Logs::General, Logs::Error, "HealRotation::m_next_cast_time_ms = %u (current_time: %u, time_diff: %i)", m_next_cast_time_ms, Timer::GetCurrentTime(), ((int32)Timer::GetCurrentTime() - (int32)m_next_cast_time_ms)); + Log.Out(Logs::General, Logs::Error, "HealRotation::m_next_poke_time_ms = %u (current_time: %u, time_diff: %i)", m_next_poke_time_ms, Timer::GetCurrentTime(), ((int32)Timer::GetCurrentTime() - (int32)m_next_poke_time_ms)); + Log.Out(Logs::General, Logs::Error, "HealRotation::m_fast_heals = %s", ((m_fast_heals) ? ("true") : ("false"))); + Log.Out(Logs::General, Logs::Error, "HealRotation::m_adaptive_targeting = %s", ((m_adaptive_targeting) ? ("true") : ("false"))); + Log.Out(Logs::General, Logs::Error, "HealRotation::m_casting_override = %s", ((m_casting_override) ? ("true") : ("false"))); + Log.Out(Logs::General, Logs::Error, "HealRotation::m_casting_target_poke = %s", ((m_casting_target_poke) ? ("true") : ("false"))); + Log.Out(Logs::General, Logs::Error, "HealRotation::m_active_heal_target = %s", ((m_active_heal_target) ? ("true") : ("false"))); + Log.Out(Logs::General, Logs::Error, "HealRotation::m_is_active = %s", ((m_is_active) ? ("true") : ("false"))); + Log.Out(Logs::General, Logs::Error, "HealRotation::m_member_list.size() = %i", m_member_pool.size()); + Log.Out(Logs::General, Logs::Error, "HealRotation::m_cycle_list.size() = %i", m_cycle_pool.size()); + Log.Out(Logs::General, Logs::Error, "HealRotation::m_target_list.size() = %i", m_target_pool.size()); + if (m_member_pool.size()) { Log.Out(Logs::General, Logs::Error, "(std::shared_ptr::use_count() = %i", m_member_pool.front()->MemberOfHealRotation()->use_count()); } + else { Log.Out(Logs::General, Logs::Error, "(std::shared_ptr::use_count() = unknown (0)"); } + Log.Out(Logs::General, Logs::Error, "HealRotation Members:"); + int member_index = 0; + for (auto mlist_iter : m_member_pool) { + if (!mlist_iter) { continue; } + Log.Out(Logs::General, Logs::Error, "(%i) %s (hrcast: %c)", (++member_index), mlist_iter->GetCleanName(), ((mlist_iter->AmICastingForHealRotation())?('T'):('F'))); + } + if (!member_index) { Log.Out(Logs::General, Logs::Error, "(0) None"); } + Log.Out(Logs::General, Logs::Error, "HealRotation Cycle:"); + int cycle_index = 0; + for (auto clist_iter : m_cycle_pool) { + if (!clist_iter) { continue; } + Log.Out(Logs::General, Logs::Error, "(%i) %s", (++cycle_index), clist_iter->GetCleanName()); + } + if (!cycle_index) { Log.Out(Logs::General, Logs::Error, "(0) None"); } + Log.Out(Logs::General, Logs::Error, "HealRotation Targets: (sort type: %u)", sort_type); + int target_index = 0; + + for (auto tlist_iter : m_target_pool) { + if (!tlist_iter) { continue; } + Log.Out(Logs::General, Logs::Error, "(%i) %s (hp: %3.1f%%, at: %u, dontheal: %c, crit(base): %c(%c), safe(base): %c(%c), hcnt(ext): %u(%u), hfreq(ext): %f(%f))", + (++target_index), tlist_iter->GetCleanName(), + tlist_iter->GetHPRatio(), + ClassArmorType(tlist_iter->GetClass()), + ((tlist_iter->DontHealMeBefore() > Timer::GetCurrentTime()) ? ('T') : ('F')), + ((tlist_iter->GetHPRatio()>m_critical_hp_ratio[ClassArmorType(tlist_iter->GetClass())]) ? ('F') : ('T')), + ((tlist_iter->GetHPRatio()>m_critical_hp_ratio[ARMOR_TYPE_UNKNOWN]) ? ('F') : ('T')), + ((tlist_iter->GetHPRatio()>m_safe_hp_ratio[ClassArmorType(tlist_iter->GetClass())]) ? ('T') : ('F')), + ((tlist_iter->GetHPRatio()>m_safe_hp_ratio[ARMOR_TYPE_UNKNOWN]) ? ('T') : ('F')), + tlist_iter->HealRotationHealCount(), + tlist_iter->HealRotationExtendedHealCount(), + tlist_iter->HealRotationHealFrequency(), + tlist_iter->HealRotationExtendedHealFrequency()); + } + if (!target_index) { Log.Out(Logs::General, Logs::Error, "(0) None (hp: 0.0\%, at: 0, dontheal: F, crit(base): F(F), safe(base): F(F), hcnt(ext): 0(0), hfreq(ext): 0.0(0.0))"); } +#endif +} + +bool HealRotation::IsMemberClass(uint8 class_id) +{ + switch (class_id) { + case CLERIC: + case DRUID: + case SHAMAN: + return true; + default: + return false; + } +} + +bool HealRotation::IsTargetMobType(Mob* target_mob) +{ + if (!target_mob) + return false; + if (!target_mob->IsClient() && !target_mob->IsBot() && !target_mob->IsPet()) + return false; + if (target_mob->IsPet() && (!target_mob->GetOwner() || (!target_mob->GetOwner()->IsClient() && !target_mob->GetOwner()->IsBot()))) + return false; + + return true; +} diff --git a/zone/heal_rotation.h b/zone/heal_rotation.h new file mode 100644 index 000000000..cfe0b0d5d --- /dev/null +++ b/zone/heal_rotation.h @@ -0,0 +1,148 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef HEAL_ROTATION_H +#define HEAL_ROTATION_H + +#include "mob.h" + +#define CASTING_CYCLE_MINIMUM_INTERVAL 1000 +#define CASTING_CYCLE_MINIMUM_INTERVAL_S 1 +#define CASTING_CYCLE_MAXIMUM_INTERVAL 30000 +#define CASTING_CYCLE_MAXIMUM_INTERVAL_S 30 +#define CASTING_CYCLE_DEFAULT_INTERVAL 5000 +#define CASTING_CYCLE_DEFAULT_INTERVAL_S 5 +#define POKE_PROPAGATION_DELAY 250 +#define ADVANCE_ROTATION_MINIMUM_INTERVAL 100 +#define HEALING_STATS_RESET_INTERVAL 60000 +#define HEALING_STATS_RESET_INTERVAL_S 60 + +#define SAFE_HP_RATIO_BASE 95.0f +#define CRITICAL_HP_RATIO_BASE 10.0f + +struct HealingStats +{ + uint32 last_heal_time_ms; + uint32 heal_count; +}; + +// Both members and targets use a shared_ptr to keep track of their HealRotation instance +class HealRotation +{ +public: + HealRotation(Bot* hr_creator, uint32 interval_ms = CASTING_CYCLE_DEFAULT_INTERVAL, bool fast_heals = false, bool adaptive_targeting = false, bool casting_override = false); + + void SetIntervalMS(uint32 interval_ms); + void SetIntervalS(uint32 interval_s); + void SetFastHeals(bool flag) { m_fast_heals = flag; } + void SetAdaptiveTargeting(bool flag) { m_adaptive_targeting = flag; } + void SetCastingOverride(bool flag) { m_casting_override = flag; } + bool AddMemberToPool(Bot* hr_member); + bool AddTargetToPool(Mob* hr_target); + + uint32 CreationTimeMS() { return m_creation_time_ms; } + uint32 LastHealTimeMS() { return m_last_heal_time_ms; } + uint32 IntervalMS() { return m_interval_ms; } + uint32 IntervalS() { return (m_interval_ms / 1000); } + bool FastHeals() { return m_fast_heals; } + bool AdaptiveTargeting() { return m_adaptive_targeting; } + bool CastingOverride() { return m_casting_override; } + bool RemoveMemberFromPool(Bot* hr_member); + bool RemoveTargetFromPool(Mob* hr_target); + + bool ClearMemberPool(); + bool ClearTargetPool(); + + bool Start(); + bool Stop(); + + bool IsActive() { return m_is_active; } + bool CastingReady() { return (Timer::GetCurrentTime() >= m_next_cast_time_ms); } + Bot* CastingMember(); + bool PokeCastingTarget(); + Mob* CastingTarget(); + bool AdvanceRotation(bool use_interval = true); + + std::list* MemberList() { return &m_member_pool; } + std::list* CycleList() { return &m_cycle_pool; } + std::list* TargetList() { return &m_target_pool; } + + bool IsMemberInPool(Bot* hr_member); + bool IsTargetInPool(Mob* hr_target); + + void SetMemberIsCasting(Bot* hr_member, bool flag = true); + bool MemberIsCasting(Bot* hr_member); + + void UpdateTargetHealingStats(Mob* hr_target); + void StartNewTargetHealingStatsCycle(uint32 current_time); + uint32 HealCount(Mob* hr_target); + uint32 ExtendedHealCount(Mob* hr_target); + float HealFrequency(Mob* hr_target); + float ExtendedHealFrequency(Mob* hr_target); + HealingStats* TargetHealingStats1(Mob* hr_target); + HealingStats* TargetHealingStats2(Mob* hr_target); + + bool SetArmorTypeSafeHPRatio(uint8 armor_type, float hp_ratio); + bool SetArmorTypeCriticalHPRatio(uint8 armor_type, float hp_ratio); + + float ArmorTypeSafeHPRatio(uint8 armor_type); + float ArmorTypeCriticalHPRatio(uint8 armor_type); + + void ResetArmorTypeHPLimits(); + + static bool IsMemberClass(uint8 class_id); + static bool IsTargetMobType(Mob* target_mob); + +private: + bool valid_state(); + void cycle_refresh(); + bool healable_target(bool use_class_at = true, bool critical_only = false); + void bias_targets(); + + uint32 m_creation_time_ms; + uint32 m_last_heal_time_ms; + uint32 m_interval_ms; + uint32 m_next_cast_time_ms; + uint32 m_next_poke_time_ms; + uint32 m_healing_stats_begin_ms; + bool m_fast_heals; + bool m_adaptive_targeting; + bool m_casting_override; + bool m_casting_target_poke; + bool m_active_heal_target; + + bool m_is_active; + + std::list m_member_pool; + std::list m_cycle_pool; + std::list m_target_pool; + + std::map m_member_is_casting; + + std::map m_target_healing_stats_1; + std::map m_target_healing_stats_2; + + float m_safe_hp_ratio[ARMOR_TYPE_COUNT]; + float m_critical_hp_ratio[ARMOR_TYPE_COUNT]; + + friend class std::_Ref_count_obj; + HealRotation(HealRotation* allocator_shunt) {}; +}; + +#endif diff --git a/zone/inventory.cpp b/zone/inventory.cpp index e1904d59f..9f7f061a0 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2003 EQEMu Development Team (http://eqemulator.net) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -2020,10 +2020,7 @@ void Client::QSSwapItemAuditor(MoveItem_Struct* move_in, bool postaction_call) { void Client::DyeArmor(DyeStruct* dye){ int16 slot=0; for (int i = EmuConstants::MATERIAL_BEGIN; i <= EmuConstants::MATERIAL_TINT_END; i++) { - if (m_pp.item_tint[i].RGB.Blue != dye->dye[i].RGB.Blue || - m_pp.item_tint[i].RGB.Red != dye->dye[i].RGB.Red || - m_pp.item_tint[i].RGB.Green != dye->dye[i].RGB.Green - ) { + if ((m_pp.item_tint[i].Color & 0x00FFFFFF) != (dye->dye[i].Color & 0x00FFFFFF)) { slot = m_inv.HasItem(32557, 1, invWherePersonal); if (slot != INVALID_INDEX){ DeleteItemInInventory(slot,1,true); diff --git a/zone/mob.cpp b/zone/mob.cpp index 0e2cd7d88..21458a2d7 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemu.org) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -473,6 +473,10 @@ Mob::~Mob() safe_delete(PathingRouteUpdateTimerShort); safe_delete(PathingRouteUpdateTimerLong); UninitializeBuffSlots(); + +#ifdef BOTS + LeaveHealRotationTargetPool(); +#endif } uint32 Mob::GetAppearanceValue(EmuAppearance iAppearance) { @@ -1766,8 +1770,7 @@ void Mob::SendIllusionPacket(uint16 in_race, uint8 in_gender, uint8 in_texture, bool Mob::RandomizeFeatures(bool send_illusion, bool set_variables) { - if (IsPlayerRace(GetRace())) - { + if (IsPlayerRace(GetRace())) { uint8 Gender = GetGender(); uint8 Texture = 0xFF; uint8 HelmTexture = 0xFF; @@ -1788,160 +1791,158 @@ bool Mob::RandomizeFeatures(bool send_illusion, bool set_variables) LuclinFace = zone->random.Int(0, 7); // Adjust all settings based on the min and max for each feature of each race and gender - switch (GetRace()) - { - case 1: // Human - HairColor = zone->random.Int(0, 19); - if (Gender == 0) { - BeardColor = HairColor; - HairStyle = zone->random.Int(0, 3); - Beard = zone->random.Int(0, 5); - } - if (Gender == 1) { - HairStyle = zone->random.Int(0, 2); - } - break; - case 2: // Barbarian - HairColor = zone->random.Int(0, 19); + switch (GetRace()) { + case HUMAN: + HairColor = zone->random.Int(0, 19); + if (Gender == MALE) { + BeardColor = HairColor; + HairStyle = zone->random.Int(0, 3); + Beard = zone->random.Int(0, 5); + } + if (Gender == FEMALE) { + HairStyle = zone->random.Int(0, 2); + } + break; + case BARBARIAN: + HairColor = zone->random.Int(0, 19); + LuclinFace = zone->random.Int(0, 87); + if (Gender == MALE) { + BeardColor = HairColor; + HairStyle = zone->random.Int(0, 3); + Beard = zone->random.Int(0, 5); + } + if (Gender == FEMALE) { + HairStyle = zone->random.Int(0, 2); + } + break; + case ERUDITE: + if (Gender == MALE) { + BeardColor = zone->random.Int(0, 19); + Beard = zone->random.Int(0, 5); + LuclinFace = zone->random.Int(0, 57); + } + if (Gender == FEMALE) { LuclinFace = zone->random.Int(0, 87); - if (Gender == 0) { - BeardColor = HairColor; - HairStyle = zone->random.Int(0, 3); - Beard = zone->random.Int(0, 5); - } - if (Gender == 1) { - HairStyle = zone->random.Int(0, 2); - } - break; - case 3: // Erudite - if (Gender == 0) { - BeardColor = zone->random.Int(0, 19); - Beard = zone->random.Int(0, 5); - LuclinFace = zone->random.Int(0, 57); - } - if (Gender == 1) { - LuclinFace = zone->random.Int(0, 87); - } - break; - case 4: // WoodElf - HairColor = zone->random.Int(0, 19); - if (Gender == 0) { - HairStyle = zone->random.Int(0, 3); - } - if (Gender == 1) { - HairStyle = zone->random.Int(0, 2); - } - break; - case 5: // HighElf - HairColor = zone->random.Int(0, 14); - if (Gender == 0) { - HairStyle = zone->random.Int(0, 3); - LuclinFace = zone->random.Int(0, 37); - BeardColor = HairColor; - } - if (Gender == 1) { - HairStyle = zone->random.Int(0, 2); - } - break; - case 6: // DarkElf - HairColor = zone->random.Int(13, 18); - if (Gender == 0) { - HairStyle = zone->random.Int(0, 3); - LuclinFace = zone->random.Int(0, 37); - BeardColor = HairColor; - } - if (Gender == 1) { - HairStyle = zone->random.Int(0, 2); - } - break; - case 7: // HalfElf - HairColor = zone->random.Int(0, 19); - if (Gender == 0) { - HairStyle = zone->random.Int(0, 3); - LuclinFace = zone->random.Int(0, 37); - BeardColor = HairColor; - } - if (Gender == 1) { - HairStyle = zone->random.Int(0, 2); - } - break; - case 8: // Dwarf - HairColor = zone->random.Int(0, 19); + } + break; + case WOOD_ELF: + HairColor = zone->random.Int(0, 19); + if (Gender == MALE) { + HairStyle = zone->random.Int(0, 3); + } + if (Gender == FEMALE) { + HairStyle = zone->random.Int(0, 2); + } + break; + case HIGH_ELF: + HairColor = zone->random.Int(0, 14); + if (Gender == MALE) { + HairStyle = zone->random.Int(0, 3); + LuclinFace = zone->random.Int(0, 37); BeardColor = HairColor; - if (Gender == 0) { - HairStyle = zone->random.Int(0, 3); - Beard = zone->random.Int(0, 5); - } - if (Gender == 1) { - HairStyle = zone->random.Int(0, 2); - LuclinFace = zone->random.Int(0, 17); - } - break; - case 9: // Troll - EyeColor1 = zone->random.Int(0, 10); - EyeColor2 = zone->random.Int(0, 10); - if (Gender == 1) { - HairStyle = zone->random.Int(0, 3); - HairColor = zone->random.Int(0, 23); - } - break; - case 10: // Ogre - if (Gender == 1) { - HairStyle = zone->random.Int(0, 3); - HairColor = zone->random.Int(0, 23); - } - break; - case 11: // Halfling - HairColor = zone->random.Int(0, 19); - if (Gender == 0) { - BeardColor = HairColor; - HairStyle = zone->random.Int(0, 3); - Beard = zone->random.Int(0, 5); - } - if (Gender == 1) { - HairStyle = zone->random.Int(0, 2); - } - break; - case 12: // Gnome - HairColor = zone->random.Int(0, 24); - if (Gender == 0) { - BeardColor = HairColor; - HairStyle = zone->random.Int(0, 3); - Beard = zone->random.Int(0, 5); - } - if (Gender == 1) { - HairStyle = zone->random.Int(0, 2); - } - break; - case 128: // Iksar - case 130: // VahShir - break; - case 330: // Froglok - LuclinFace = zone->random.Int(0, 9); - case 522: // Drakkin - HairColor = zone->random.Int(0, 3); + } + if (Gender == FEMALE) { + HairStyle = zone->random.Int(0, 2); + } + break; + case DARK_ELF: + HairColor = zone->random.Int(13, 18); + if (Gender == MALE) { + HairStyle = zone->random.Int(0, 3); + LuclinFace = zone->random.Int(0, 37); BeardColor = HairColor; - EyeColor1 = zone->random.Int(0, 11); - EyeColor2 = zone->random.Int(0, 11); - LuclinFace = zone->random.Int(0, 6); - DrakkinHeritage = zone->random.Int(0, 6); - DrakkinTattoo = zone->random.Int(0, 7); - DrakkinDetails = zone->random.Int(0, 7); - if (Gender == 0) { - Beard = zone->random.Int(0, 12); - HairStyle = zone->random.Int(0, 8); - } - if (Gender == 1) { - Beard = zone->random.Int(0, 3); - HairStyle = zone->random.Int(0, 7); - } - break; - default: - break; + } + if (Gender == FEMALE) { + HairStyle = zone->random.Int(0, 2); + } + break; + case HALF_ELF: + HairColor = zone->random.Int(0, 19); + if (Gender == MALE) { + HairStyle = zone->random.Int(0, 3); + LuclinFace = zone->random.Int(0, 37); + BeardColor = HairColor; + } + if (Gender == FEMALE) { + HairStyle = zone->random.Int(0, 2); + } + break; + case DWARF: + HairColor = zone->random.Int(0, 19); + BeardColor = HairColor; + if (Gender == MALE) { + HairStyle = zone->random.Int(0, 3); + Beard = zone->random.Int(0, 5); + } + if (Gender == FEMALE) { + HairStyle = zone->random.Int(0, 2); + LuclinFace = zone->random.Int(0, 17); + } + break; + case TROLL: + EyeColor1 = zone->random.Int(0, 10); + EyeColor2 = zone->random.Int(0, 10); + if (Gender == FEMALE) { + HairStyle = zone->random.Int(0, 3); + HairColor = zone->random.Int(0, 23); + } + break; + case OGRE: + if (Gender == FEMALE) { + HairStyle = zone->random.Int(0, 3); + HairColor = zone->random.Int(0, 23); + } + break; + case HALFLING: + HairColor = zone->random.Int(0, 19); + if (Gender == MALE) { + BeardColor = HairColor; + HairStyle = zone->random.Int(0, 3); + Beard = zone->random.Int(0, 5); + } + if (Gender == FEMALE) { + HairStyle = zone->random.Int(0, 2); + } + break; + case GNOME: + HairColor = zone->random.Int(0, 24); + if (Gender == MALE) { + BeardColor = HairColor; + HairStyle = zone->random.Int(0, 3); + Beard = zone->random.Int(0, 5); + } + if (Gender == FEMALE) { + HairStyle = zone->random.Int(0, 2); + } + break; + case IKSAR: + case VAHSHIR: + break; + case FROGLOK: + LuclinFace = zone->random.Int(0, 9); + case DRAKKIN: + HairColor = zone->random.Int(0, 3); + BeardColor = HairColor; + EyeColor1 = zone->random.Int(0, 11); + EyeColor2 = zone->random.Int(0, 11); + LuclinFace = zone->random.Int(0, 6); + DrakkinHeritage = zone->random.Int(0, 6); + DrakkinTattoo = zone->random.Int(0, 7); + DrakkinDetails = zone->random.Int(0, 7); + if (Gender == MALE) { + Beard = zone->random.Int(0, 12); + HairStyle = zone->random.Int(0, 8); + } + if (Gender == FEMALE) { + Beard = zone->random.Int(0, 3); + HairStyle = zone->random.Int(0, 7); + } + break; + default: + break; } - if (set_variables) - { + if (set_variables) { haircolor = HairColor; beardcolor = BeardColor; eyecolor1 = EyeColor1; @@ -1954,8 +1955,7 @@ bool Mob::RandomizeFeatures(bool send_illusion, bool set_variables) drakkin_details = DrakkinDetails; } - if (send_illusion) - { + if (send_illusion) { SendIllusionPacket(GetRace(), Gender, Texture, HelmTexture, HairColor, BeardColor, EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, DrakkinHeritage, DrakkinTattoo, DrakkinDetails); @@ -5861,3 +5861,69 @@ int Mob::CheckBaneDamage(const ItemInst *item) return damage; } + +#ifdef BOTS +bool Mob::JoinHealRotationTargetPool(std::shared_ptr* heal_rotation) +{ + if (IsHealRotationTarget()) + return false; + if (!heal_rotation->use_count()) + return false; + if (!(*heal_rotation)) + return false; + if (!HealRotation::IsTargetMobType(this)) + return false; + + if (!(*heal_rotation)->AddTargetToPool(this)) + return false; + + m_target_of_heal_rotation = *heal_rotation; + + return IsHealRotationTarget(); +} + +bool Mob::LeaveHealRotationTargetPool() +{ + if (!IsHealRotationTarget()) { + m_target_of_heal_rotation.reset(); + return true; + } + + m_target_of_heal_rotation->RemoveTargetFromPool(this); + m_target_of_heal_rotation.reset(); + + return !IsHealRotationTarget(); +} + +uint32 Mob::HealRotationHealCount() +{ + if (!IsHealRotationTarget()) + return 0; + + return m_target_of_heal_rotation->HealCount(this); +} + +uint32 Mob::HealRotationExtendedHealCount() +{ + if (!IsHealRotationTarget()) + return 0; + + return m_target_of_heal_rotation->ExtendedHealCount(this); +} + +float Mob::HealRotationHealFrequency() +{ + if (!IsHealRotationTarget()) + return 0.0f; + + return m_target_of_heal_rotation->HealFrequency(this); +} + +float Mob::HealRotationExtendedHealFrequency() +{ + if (!IsHealRotationTarget()) + return 0.0f; + + return m_target_of_heal_rotation->ExtendedHealFrequency(this); +} +#endif diff --git a/zone/mob.h b/zone/mob.h index c4a75b5aa..dd8a36c89 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemu.org) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,6 +29,10 @@ #include #include +#ifdef BOTS +#include "heal_rotation.h" +#endif + char* strn0cpy(char* dest, const char* source, uint32 size); #define MAX_SPECIAL_ATTACK_PARAMS 8 @@ -1005,6 +1009,20 @@ public: void DelAssistCap() { --npc_assist_cap; } void ResetAssistCap() { npc_assist_cap = 0; } + // Bots HealRotation methods +#ifdef BOTS + bool IsHealRotationTarget() { return (m_target_of_heal_rotation.use_count() && m_target_of_heal_rotation.get()); } + bool JoinHealRotationTargetPool(std::shared_ptr* heal_rotation); + bool LeaveHealRotationTargetPool(); + + uint32 HealRotationHealCount(); + uint32 HealRotationExtendedHealCount(); + float HealRotationHealFrequency(); + float HealRotationExtendedHealFrequency(); + + const std::shared_ptr* TargetOfHealRotation() const { return &m_target_of_heal_rotation; } +#endif + protected: void CommonDamage(Mob* other, int32 &damage, const uint16 spell_id, const SkillUseTypes attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic, int special = 0); static uint16 GetProcID(uint16 spell_id, uint8 effect_index); @@ -1373,6 +1391,11 @@ protected: private: void _StopSong(); //this is not what you think it is Mob* target; + +#ifdef BOTS + std::shared_ptr m_target_of_heal_rotation; +#endif + }; #endif diff --git a/zone/net.cpp b/zone/net.cpp index 0d5d546d6..194e017fe 100644 --- a/zone/net.cpp +++ b/zone/net.cpp @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemu.org) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -52,6 +52,10 @@ #include "zone.h" #include "queryserv.h" #include "command.h" +#ifdef BOTS +#include "bot_command.h" +#include "bot_database.h" +#endif #include "zone_config.h" #include "titles.h" #include "guild_mgr.h" @@ -203,6 +207,18 @@ int main(int argc, char** argv) { return 1; } +#ifdef BOTS + if (!botdb.Connect( + Config->DatabaseHost.c_str(), + Config->DatabaseUsername.c_str(), + Config->DatabasePassword.c_str(), + Config->DatabaseDB.c_str(), + Config->DatabasePort)) { + Log.Out(Logs::General, Logs::Error, "Cannot continue without a bots database connection."); + return 1; + } +#endif + /* Register Log System and Settings */ Log.OnLogHookCallBackZone(&Zone::GMSayHookCallBackProcess); database.LoadLogSettings(Log.log_settings); @@ -325,6 +341,15 @@ int main(int argc, char** argv) { } } +#ifdef BOTS + Log.Out(Logs::General, Logs::Zone_Server, "Loading bot commands"); + int botretval = bot_command_init(); + if (botretval<0) + Log.Out(Logs::General, Logs::Error, "Bot command loading FAILED"); + else + Log.Out(Logs::General, Logs::Zone_Server, "%d bot commands loaded", botretval); +#endif + if(RuleB(TaskSystem, EnableTaskSystem)) { Log.Out(Logs::General, Logs::Tasks, "[INIT] Loading Tasks"); taskmanager = new TaskManager; @@ -524,6 +549,9 @@ int main(int argc, char** argv) { worldserver.Disconnect(); safe_delete(taskmanager); command_deinit(); +#ifdef BOTS + bot_command_deinit(); +#endif safe_delete(parse); Log.Out(Logs::General, Logs::Zone_Server, "Proper zone shutdown complete."); Log.CloseFileLogs(); diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index 5f32992bc..9914fd15d 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -1,5 +1,5 @@ /* EQEMu: Everquest Server Emulator - Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) + Copyright (C) 2001-2016 EQEMu Development Team (http://eqemu.org) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1994,7 +1994,7 @@ bool WorldServer::SendVoiceMacro(Client* From, uint32 Type, char* Target, uint32 svm->Type = Type; - svm->Voice = (GetArrayRace(From->GetRace()) * 2) + From->GetGender(); + svm->Voice = (GetPlayerRaceValue(From->GetRace()) * 2) + From->GetGender(); svm->MacroNumber = MacroNumber;