diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 95dc420ee..b595709c6 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -33,9 +33,6 @@ */ #include -#include -#include -#include #ifdef _WINDOWS #define strcasecmp _stricmp @@ -53,16 +50,12 @@ #include "bot_command.h" #include "zonedb.h" -#include "map.h" -#include "doors.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" -#include "dialogue_window.h" #include "mob.h" #include @@ -70,40 +63,14 @@ extern QueryServ* QServ; extern WorldServer worldserver; extern TaskManager *task_manager; -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 HP_RATIO_DELTA 5.0f - - enum { EffectIDFirst = 1, EffectIDLast = 12 }; - -#define VALIDATECLASSID(x) ((x >= Class::Warrior && x <= Class::Berserker) ? (x) : (0)) -#define CLASSIDTOINDEX(x) ((x >= Class::Warrior && x <= Class::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_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; @@ -1259,76 +1226,21 @@ private: #endif }; +int bot_command_count; -//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; - - 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(Chat::White, "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(); @@ -1341,40 +1253,40 @@ int bot_command_init(void) bot_command_add("attack", "Orders bots to attack a designated target", AccountStatus::Player, bot_command_attack) || bot_command_add("bindaffinity", "Orders a bot to attempt an affinity binding", AccountStatus::Player, bot_command_bind_affinity) || bot_command_add("bot", "Lists the available bot management [subcommands]", AccountStatus::Player, bot_command_bot) || - bot_command_add("botappearance", "Lists the available bot appearance [subcommands]", AccountStatus::Player, bot_subcommand_bot_appearance) || - bot_command_add("botbeardcolor", "Changes the beard color of a bot", AccountStatus::Player, bot_subcommand_bot_beard_color) || - bot_command_add("botbeardstyle", "Changes the beard style of a bot", AccountStatus::Player, bot_subcommand_bot_beard_style) || - bot_command_add("botcamp", "Orders a bot(s) to camp", AccountStatus::Player, bot_subcommand_bot_camp) || - bot_command_add("botclone", "Creates a copy of a bot", AccountStatus::GMMgmt, bot_subcommand_bot_clone) || - bot_command_add("botcreate", "Creates a new bot", AccountStatus::Player, bot_subcommand_bot_create) || - bot_command_add("botdelete", "Deletes all record of a bot", AccountStatus::Player, bot_subcommand_bot_delete) || - bot_command_add("botdetails", "Changes the Drakkin details of a bot", AccountStatus::Player, bot_subcommand_bot_details) || - bot_command_add("botdyearmor", "Changes the color of a bot's (bots') armor", AccountStatus::Player, bot_subcommand_bot_dye_armor) || - bot_command_add("boteyes", "Changes the eye colors of a bot", AccountStatus::Player, bot_subcommand_bot_eyes) || - bot_command_add("botface", "Changes the facial appearance of your bot", AccountStatus::Player, bot_subcommand_bot_face) || - bot_command_add("botfollowdistance", "Changes the follow distance(s) of a bot(s)", AccountStatus::Player, bot_subcommand_bot_follow_distance) || - bot_command_add("bothaircolor", "Changes the hair color of a bot", AccountStatus::Player, bot_subcommand_bot_hair_color) || - bot_command_add("bothairstyle", "Changes the hairstyle of a bot", AccountStatus::Player, bot_subcommand_bot_hairstyle) || - bot_command_add("botheritage", "Changes the Drakkin heritage of a bot", AccountStatus::Player, bot_subcommand_bot_heritage) || - bot_command_add("botinspectmessage", "Changes the inspect message of a bot", AccountStatus::Player, bot_subcommand_bot_inspect_message) || - bot_command_add("botlist", "Lists the bots that you own", AccountStatus::Player, 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", AccountStatus::Player, bot_subcommand_bot_out_of_combat) || - bot_command_add("botreport", "Orders a bot to report its readiness", AccountStatus::Player, bot_subcommand_bot_report) || - bot_command_add("botspawn", "Spawns a created bot", AccountStatus::Player, bot_subcommand_bot_spawn) || - bot_command_add("botstance", "Changes the stance of a bot", AccountStatus::Player, bot_subcommand_bot_stance) || - bot_command_add("botstopmeleelevel", "Sets the level a caster or spell-casting fighter bot will stop melee combat", AccountStatus::Player, bot_subcommand_bot_stop_melee_level) || - bot_command_add("botsuffix", "Sets a bots suffix", AccountStatus::Player, bot_subcommand_bot_suffix) || - bot_command_add("botsummon", "Summons bot(s) to your location", AccountStatus::Player, bot_subcommand_bot_summon) || - bot_command_add("botsurname", "Sets a bots surname (last name)", AccountStatus::Player, bot_subcommand_bot_surname) || - bot_command_add("bottattoo", "Changes the Drakkin tattoo of a bot", AccountStatus::Player, bot_subcommand_bot_tattoo) || - bot_command_add("bottogglearcher", "Toggles a archer bot between melee and ranged weapon use", AccountStatus::Player, bot_subcommand_bot_toggle_archer) || - bot_command_add("bottogglehelm", "Toggles the helm visibility of a bot between shown and hidden", AccountStatus::Player, bot_subcommand_bot_toggle_helm) || - bot_command_add("bottitle", "Sets a bots title", AccountStatus::Player, bot_subcommand_bot_title) || - bot_command_add("botupdate", "Updates a bot to reflect any level changes that you have experienced", AccountStatus::Player, bot_subcommand_bot_update) || - bot_command_add("botwoad", "Changes the Barbarian woad of a bot", AccountStatus::Player, bot_subcommand_bot_woad) || + bot_command_add("botappearance", "Lists the available bot appearance [subcommands]", AccountStatus::Player, bot_command_appearance) || + bot_command_add("botbeardcolor", "Changes the beard color of a bot", AccountStatus::Player, bot_command_beard_color) || + bot_command_add("botbeardstyle", "Changes the beard style of a bot", AccountStatus::Player, bot_command_beard_style) || + bot_command_add("botcamp", "Orders a bot(s) to camp", AccountStatus::Player, bot_command_camp) || + bot_command_add("botclone", "Creates a copy of a bot", AccountStatus::GMMgmt, bot_command_clone) || + bot_command_add("botcreate", "Creates a new bot", AccountStatus::Player, bot_command_create) || + bot_command_add("botdelete", "Deletes all record of a bot", AccountStatus::Player, bot_command_delete) || + bot_command_add("botdetails", "Changes the Drakkin details of a bot", AccountStatus::Player, bot_command_details) || + bot_command_add("botdyearmor", "Changes the color of a bot's (bots') armor", AccountStatus::Player, bot_command_dye_armor) || + bot_command_add("boteyes", "Changes the eye colors of a bot", AccountStatus::Player, bot_command_eyes) || + bot_command_add("botface", "Changes the facial appearance of your bot", AccountStatus::Player, bot_command_face) || + bot_command_add("botfollowdistance", "Changes the follow distance(s) of a bot(s)", AccountStatus::Player, bot_command_follow_distance) || + bot_command_add("bothaircolor", "Changes the hair color of a bot", AccountStatus::Player, bot_command_hair_color) || + bot_command_add("bothairstyle", "Changes the hairstyle of a bot", AccountStatus::Player, bot_command_hairstyle) || + bot_command_add("botheritage", "Changes the Drakkin heritage of a bot", AccountStatus::Player, bot_command_heritage) || + bot_command_add("botinspectmessage", "Changes the inspect message of a bot", AccountStatus::Player, bot_command_inspect_message) || + bot_command_add("botlist", "Lists the bots that you own", AccountStatus::Player, bot_command_list_bots) || + bot_command_add("botoutofcombat", "Toggles your bot between standard and out-of-combat spell/skill use - if any specialized behaviors exist", AccountStatus::Player, bot_command_out_of_combat) || + bot_command_add("botreport", "Orders a bot to report its readiness", AccountStatus::Player, bot_command_report) || + bot_command_add("botspawn", "Spawns a created bot", AccountStatus::Player, bot_command_spawn) || + bot_command_add("botstance", "Changes the stance of a bot", AccountStatus::Player, bot_command_stance) || + bot_command_add("botstopmeleelevel", "Sets the level a caster or spell-casting fighter bot will stop melee combat", AccountStatus::Player, bot_command_stop_melee_level) || + bot_command_add("botsuffix", "Sets a bots suffix", AccountStatus::Player, bot_command_suffix) || + bot_command_add("botsummon", "Summons bot(s) to your location", AccountStatus::Player, bot_command_summon) || + bot_command_add("botsurname", "Sets a bots surname (last name)", AccountStatus::Player, bot_command_surname) || + bot_command_add("bottattoo", "Changes the Drakkin tattoo of a bot", AccountStatus::Player, bot_command_tattoo) || + bot_command_add("bottogglearcher", "Toggles a archer bot between melee and ranged weapon use", AccountStatus::Player, bot_command_toggle_archer) || + bot_command_add("bottogglehelm", "Toggles the helm visibility of a bot between shown and hidden", AccountStatus::Player, bot_command_toggle_helm) || + bot_command_add("bottitle", "Sets a bots title", AccountStatus::Player, bot_command_title) || + bot_command_add("botupdate", "Updates a bot to reflect any level changes that you have experienced", AccountStatus::Player, bot_command_update) || + bot_command_add("botwoad", "Changes the Barbarian woad of a bot", AccountStatus::Player, bot_command_woad) || bot_command_add("casterrange", "Controls the range casters will try to stay away from a mob (if too far, they will skip spells that are out-of-range)", AccountStatus::Player, bot_command_caster_range) || bot_command_add("charm", "Attempts to have a bot charm your target", AccountStatus::Player, bot_command_charm) || - bot_command_add("circle", "Orders a Druid bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_subcommand_circle) || + bot_command_add("circle", "Orders a Druid bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_command_circle) || bot_command_add("clickitem", "Orders your targeted bot to click the item in the provided inventory slot.", AccountStatus::Player, bot_command_click_item) || bot_command_add("cure", "Orders a bot to remove any ailments", AccountStatus::Player, bot_command_cure) || bot_command_add("defensive", "Orders a bot to use a defensive discipline", AccountStatus::Player, bot_command_defensive) || @@ -1385,34 +1297,34 @@ int bot_command_init(void) bot_command_add("follow", "Orders bots to follow a designated target (option 'chain' auto-links eligible spawned bots)", AccountStatus::Player, bot_command_follow) || bot_command_add("guard", "Orders bots to guard their current positions", AccountStatus::Player, bot_command_guard) || bot_command_add("healrotation", "Lists the available bot heal rotation [subcommands]", AccountStatus::Player, bot_command_heal_rotation) || - bot_command_add("healrotationadaptivetargeting", "Enables or disables adaptive targeting within the heal rotation instance", AccountStatus::Player, bot_subcommand_heal_rotation_adaptive_targeting) || - bot_command_add("healrotationaddmember", "Adds a bot to a heal rotation instance", AccountStatus::Player, bot_subcommand_heal_rotation_add_member) || - bot_command_add("healrotationaddtarget", "Adds target to a heal rotation instance", AccountStatus::Player, 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", AccountStatus::Player, 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", AccountStatus::Player, bot_subcommand_heal_rotation_adjust_safe) || - bot_command_add("healrotationcastingoverride", "Enables or disables casting overrides within the heal rotation instance", AccountStatus::Player, bot_subcommand_heal_rotation_casting_override) || - bot_command_add("healrotationchangeinterval", "Changes casting interval between members within the heal rotation instance", AccountStatus::Player, bot_subcommand_heal_rotation_change_interval) || - bot_command_add("healrotationclearhot", "Clears the HOT of a heal rotation instance", AccountStatus::Player, bot_subcommand_heal_rotation_clear_hot) || - bot_command_add("healrotationcleartargets", "Removes all targets from a heal rotation instance", AccountStatus::Player, bot_subcommand_heal_rotation_clear_targets) || - bot_command_add("healrotationcreate", "Creates a bot heal rotation instance and designates a leader", AccountStatus::Player, bot_subcommand_heal_rotation_create) || - bot_command_add("healrotationdelete", "Deletes a bot heal rotation entry by leader", AccountStatus::Player, bot_subcommand_heal_rotation_delete) || - bot_command_add("healrotationfastheals", "Enables or disables fast heals within the heal rotation instance", AccountStatus::Player, bot_subcommand_heal_rotation_fast_heals) || - bot_command_add("healrotationlist", "Reports heal rotation instance(s) information", AccountStatus::Player, bot_subcommand_heal_rotation_list) || - bot_command_add("healrotationremovemember", "Removes a bot from a heal rotation instance", AccountStatus::Player, bot_subcommand_heal_rotation_remove_member) || - bot_command_add("healrotationremovetarget", "Removes target from a heal rotations instance", AccountStatus::Player, 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", AccountStatus::Player, bot_subcommand_heal_rotation_reset_limits) || - bot_command_add("healrotationsave", "Saves a bot heal rotation entry by leader", AccountStatus::Player, bot_subcommand_heal_rotation_save) || - bot_command_add("healrotationsethot", "Sets the HOT in a heal rotation instance", AccountStatus::Player, bot_subcommand_heal_rotation_set_hot) || - bot_command_add("healrotationstart", "Starts a heal rotation", AccountStatus::Player, bot_subcommand_heal_rotation_start) || - bot_command_add("healrotationstop", "Stops a heal rotation", AccountStatus::Player, bot_subcommand_heal_rotation_stop) || + bot_command_add("healrotationadaptivetargeting", "Enables or disables adaptive targeting within the heal rotation instance", AccountStatus::Player, bot_command_heal_rotation_adaptive_targeting) || + bot_command_add("healrotationaddmember", "Adds a bot to a heal rotation instance", AccountStatus::Player, bot_command_heal_rotation_add_member) || + bot_command_add("healrotationaddtarget", "Adds target to a heal rotation instance", AccountStatus::Player, bot_command_heal_rotation_add_target) || + bot_command_add("healrotationadjustcritical", "Adjusts the critial HP limit of the heal rotation instance's Class Armor Type criteria", AccountStatus::Player, bot_command_heal_rotation_adjust_critical) || + bot_command_add("healrotationadjustsafe", "Adjusts the safe HP limit of the heal rotation instance's Class Armor Type criteria", AccountStatus::Player, bot_command_heal_rotation_adjust_safe) || + bot_command_add("healrotationcastingoverride", "Enables or disables casting overrides within the heal rotation instance", AccountStatus::Player, bot_command_heal_rotation_casting_override) || + bot_command_add("healrotationchangeinterval", "Changes casting interval between members within the heal rotation instance", AccountStatus::Player, bot_command_heal_rotation_change_interval) || + bot_command_add("healrotationclearhot", "Clears the HOT of a heal rotation instance", AccountStatus::Player, bot_command_heal_rotation_clear_hot) || + bot_command_add("healrotationcleartargets", "Removes all targets from a heal rotation instance", AccountStatus::Player, bot_command_heal_rotation_clear_targets) || + bot_command_add("healrotationcreate", "Creates a bot heal rotation instance and designates a leader", AccountStatus::Player, bot_command_heal_rotation_create) || + bot_command_add("healrotationdelete", "Deletes a bot heal rotation entry by leader", AccountStatus::Player, bot_command_heal_rotation_delete) || + bot_command_add("healrotationfastheals", "Enables or disables fast heals within the heal rotation instance", AccountStatus::Player, bot_command_heal_rotation_fast_heals) || + bot_command_add("healrotationlist", "Reports heal rotation instance(s) information", AccountStatus::Player, bot_command_heal_rotation_list) || + bot_command_add("healrotationremovemember", "Removes a bot from a heal rotation instance", AccountStatus::Player, bot_command_heal_rotation_remove_member) || + bot_command_add("healrotationremovetarget", "Removes target from a heal rotations instance", AccountStatus::Player, bot_command_heal_rotation_remove_target) || + bot_command_add("healrotationresetlimits", "Resets all Class Armor Type HP limit criteria in a heal rotation to its default value", AccountStatus::Player, bot_command_heal_rotation_reset_limits) || + bot_command_add("healrotationsave", "Saves a bot heal rotation entry by leader", AccountStatus::Player, bot_command_heal_rotation_save) || + bot_command_add("healrotationsethot", "Sets the HOT in a heal rotation instance", AccountStatus::Player, bot_command_heal_rotation_set_hot) || + bot_command_add("healrotationstart", "Starts a heal rotation", AccountStatus::Player, bot_command_heal_rotation_start) || + bot_command_add("healrotationstop", "Stops a heal rotation", AccountStatus::Player, bot_command_heal_rotation_stop) || bot_command_add("help", "List available commands and their description - specify partial command as argument to search", AccountStatus::Player, bot_command_help) || bot_command_add("hold", "Prevents a bot from attacking until released", AccountStatus::Player, bot_command_hold) || bot_command_add("identify", "Orders a bot to cast an item identification spell", AccountStatus::Player, bot_command_identify) || bot_command_add("inventory", "Lists the available bot inventory [subcommands]", AccountStatus::Player, bot_command_inventory) || - bot_command_add("inventorygive", "Gives the item on your cursor to a bot", AccountStatus::Player, bot_subcommand_inventory_give) || - bot_command_add("inventorylist", "Lists all items in a bot's inventory", AccountStatus::Player, bot_subcommand_inventory_list) || - bot_command_add("inventoryremove", "Removes an item from a bot's inventory", AccountStatus::Player, bot_subcommand_inventory_remove) || - bot_command_add("inventorywindow", "Displays all items in a bot's inventory in a pop-up window", AccountStatus::Player, bot_subcommand_inventory_window) || + bot_command_add("inventorygive", "Gives the item on your cursor to a bot", AccountStatus::Player, bot_command_inventory_give) || + bot_command_add("inventorylist", "Lists all items in a bot's inventory", AccountStatus::Player, bot_command_inventory_list) || + bot_command_add("inventoryremove", "Removes an item from a bot's inventory", AccountStatus::Player, bot_command_inventory_remove) || + bot_command_add("inventorywindow", "Displays all items in a bot's inventory in a pop-up window", AccountStatus::Player, bot_command_inventory_window) || bot_command_add("invisibility", "Orders a bot to cast a cloak of invisibility, or allow them to be seen", AccountStatus::Player, bot_command_invisibility) || bot_command_add("itemuse", "Elicits a report from spawned bots that can use the item on your cursor (option 'empty' yields only empty slots)", AccountStatus::Player, bot_command_item_use) || bot_command_add("levitation", "Orders a bot to cast a levitation spell", AccountStatus::Player, bot_command_levitation) || @@ -1421,13 +1333,13 @@ int bot_command_init(void) bot_command_add("movementspeed", "Orders a bot to cast a movement speed enhancement spell", AccountStatus::Player, bot_command_movement_speed) || bot_command_add("owneroption", "Sets options available to bot owners", AccountStatus::Player, bot_command_owner_option) || bot_command_add("pet", "Lists the available bot pet [subcommands]", AccountStatus::Player, bot_command_pet) || - bot_command_add("petgetlost", "Orders a bot to remove its summoned pet", AccountStatus::Player, bot_subcommand_pet_get_lost) || - bot_command_add("petremove", "Orders a bot to remove its charmed pet", AccountStatus::Player, bot_subcommand_pet_remove) || - bot_command_add("petsettype", "Orders a Magician bot to use a specified pet type", AccountStatus::Player, bot_subcommand_pet_set_type) || + bot_command_add("petgetlost", "Orders a bot to remove its summoned pet", AccountStatus::Player, bot_command_pet_get_lost) || + bot_command_add("petremove", "Orders a bot to remove its charmed pet", AccountStatus::Player, bot_command_pet_remove) || + bot_command_add("petsettype", "Orders a Magician bot to use a specified pet type", AccountStatus::Player, bot_command_pet_set_type) || bot_command_add("picklock", "Orders a capable bot to pick the lock of the closest door", AccountStatus::Player, bot_command_pick_lock) || bot_command_add("pickpocket", "Orders a capable bot to pickpocket a NPC", AccountStatus::Player, bot_command_pickpocket) || bot_command_add("precombat", "Sets flag used to determine pre-combat behavior", AccountStatus::Player, bot_command_precombat) || - bot_command_add("portal", "Orders a Wizard bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_subcommand_portal) || + bot_command_add("portal", "Orders a Wizard bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_command_portal) || bot_command_add("pull", "Orders a designated bot to 'pull' an enemy", AccountStatus::Player, bot_command_pull) || bot_command_add("release", "Releases a suspended bot's AI processing (with hate list wipe)", AccountStatus::Player, bot_command_release) || bot_command_add("resistance", "Orders a bot to cast a specified resistance buff", AccountStatus::Player, bot_command_resistance) || @@ -1551,15 +1463,6 @@ int bot_command_init(void) 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(); @@ -1571,18 +1474,6 @@ void bot_command_deinit(void) 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()) { @@ -1617,19 +1508,6 @@ int bot_command_add(std::string bot_command_name, const char *desc, int access, 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 @@ -1667,7714 +1545,6 @@ int bot_command_real_dispatch(Client *c, const char *message) } -/* - * 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() && !bot_owner->GetRaid()) { - return; - } - - if (bot_owner->IsRaidGrouped()) { - Raid* raid = bot_owner->GetRaid(); - if (!raid) { - return; - } - - uint32 raid_group = raid->GetGroup(bot_owner); - if (raid_group >= MAX_RAID_GROUPS) { - return; - } - - for (const auto& m : raid->members) { - if (m.member && m.group_number == raid_group) { - if (!MyBots::IsMyBot(bot_owner, m.member->CastToBot())) { - continue; - } - - sbl.push_back(m.member->CastToBot()); - } - } - } - else { - 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_ByMyRaidBots(Client* bot_owner, std::list& sbl, bool clear_list = true) { - if (clear_list) { - sbl.clear(); - } - - if (!bot_owner) { - return; - } - - if (!bot_owner->GetRaid()) { - return; - } - - Raid* raid = bot_owner->GetRaid(); - if (!raid) { - return; - } - - for (const auto& m : raid->members) { - if (m.member) { - if (!MyBots::IsMyBot(bot_owner, m.member->CastToBot())) { - continue; - } - - sbl.push_back(m.member->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->GetRaid()) || (!target_mob->IsClient() && !target_mob->IsBot())) { - return; - } - - if (bot_owner->IsRaidGrouped()) { - Raid* raid = bot_owner->GetRaid(); - if (!raid) { - return; - } - - if (MyBots::IsMyBot(bot_owner, target_mob)) { - uint32 raid_group = raid->GetGroup(target_mob->CastToClient()); - if (raid_group >= MAX_RAID_GROUPS) { - return; - } - - for (const auto& m : raid->members) { - if (m.member && m.group_number == raid_group) { - if (!MyBots::IsMyBot(bot_owner, m.member->CastToBot())) { - continue; - } - - sbl.push_back(m.member->CastToBot()); - } - } - } - } - else { - 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_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 || !MyBots::IsMyBot(bot_owner, named_mob) || (!named_mob->GetGroup() && !named_mob->GetRaid()) || (!named_mob->IsClient() && !named_mob->IsBot())) { - return; - } - - if (bot_owner->IsRaidGrouped()) { - Raid* raid = bot_owner->GetRaid(); - if (!raid) { - return; - } - - uint32 raid_group = raid->GetGroup(named_mob->CastToClient()); - if (raid_group >= MAX_RAID_GROUPS) { - return; - } - - for (const auto& m : raid->members) { - if (m.member && m.group_number == raid_group) { - if (!MyBots::IsMyBot(bot_owner, m.member->CastToBot())) { - continue; - } - - sbl.push_back(m.member->CastToBot()); - } - } - } - else { - 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_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); - } - - static void PopulateSBL_BySpawnedBotsClass(Client * bot_owner, std::list &sbl, uint16 cls, bool clear_list = true) { - if (clear_list) { - sbl.clear(); - } - - if (!bot_owner || !cls) { - return; - } - - auto selectable_bot_list = entity_list.GetBotsByBotOwnerCharacterID(bot_owner->CharacterID()); - for (auto bot_iter : selectable_bot_list) { - if (bot_iter->GetClass() != cls) { - continue; - } - - sbl.push_back(bot_iter); - } - - if (!clear_list) { - UniquifySBL(sbl); - } - } - - static void PopulateSBL_BySpawnedBotsRace(Client* bot_owner, std::list& sbl, uint16 race, bool clear_list = true) { - if (clear_list) { - sbl.clear(); - } - - if (!bot_owner || !race) { - return; - } - - auto selectable_bot_list = entity_list.GetBotsByBotOwnerCharacterID(bot_owner->CharacterID()); - for (auto bot_iter : selectable_bot_list) { - if (bot_iter->GetBaseRace() != race) { - continue; - } - - sbl.push_back(bot_iter); - } - - if (!clear_list) { - UniquifySBL(sbl); - } - } -}; - -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 bool IsAttackable(Client *bot_owner, Mob* target_mob) { - if (!bot_owner || !target_mob || bot_owner == target_mob) - return false; - - return bot_owner->IsAttackAllowed(target_mob); - } - - 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 (!IsAttackable(bot_owner, 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 (IsAttackable(bot_owner, bot_owner->GetTarget()) || target_type == BCEnum::TT_None) - return nullptr; - - auto target_mob = bot_owner->GetTarget(); - 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 (!IsAttackable(bot_owner, bot_owner->GetTarget()) || target_type == BCEnum::TT_None) - return nullptr; - - auto target_mob = bot_owner->GetTarget(); - 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_OwnerRaid, - ABT_TargetGroup, - ABT_NamesGroup, - ABT_HealRotation, - ABT_HealRotationMembers, - ABT_HealRotationTargets, - ABT_Class, - ABT_Race, - 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_OwnerRaid = (1 << (ABT_OwnerRaid - 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_Class = (1 << (ABT_Class - 1)), - ABM_Race = (1 << (ABT_Race - 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_OwnerRaid | ABM_TargetGroup | ABM_NamesGroup | ABM_HealRotationTargets | ABM_Spawned | ABM_Class | ABM_Race), - ABM_Type2 = (ABM_ByName | ABM_OwnerGroup | ABM_OwnerRaid | ABM_NamesGroup | ABM_HealRotation | ABM_Spawned | ABM_Class | ABM_Race) - }; - - // Populates 'sbl' - static ABType PopulateSBL(Client* bot_owner, std::string ab_type_arg, std::list &sbl, int ab_mask, const char* name = nullptr, uint16 classrace = 0, 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("ownerraid")) { - ab_type = ABT_OwnerRaid; - } - 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("byclass")) { - ab_type = ABT_Class; - } - else if (!ab_type_arg.compare("byrace")) { - ab_type = ABT_Race; - } - 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_OwnerRaid: - if (ab_mask & ABM_OwnerRaid) { - MyBots::PopulateSBL_ByMyRaidBots(bot_owner, sbl, 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_Class: - if (ab_mask & ABM_Class) { - MyBots::PopulateSBL_BySpawnedBotsClass(bot_owner, sbl, classrace, clear_list); - } - break; - case ABT_Race: - if (ab_mask & ABM_Race) { - MyBots::PopulateSBL_BySpawnedBotsRace(bot_owner, sbl, classrace, 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(Chat::White, "Command passed null 'ActionableBot' criteria"); - } - else if (ab_mask & ab_type) { - if (classrace) { - bot_owner->Message(Chat::White, "You have no spawned bots meeting this criteria - type: '%s', %s: '%i'", ab_type_arg.c_str(), ab_mask & ABM_Class ? "class" : ab_mask & ABM_Race ? "race" : "", classrace); - } - else { - bot_owner->Message(Chat::White, "You have no spawned bots meeting this criteria - type: '%s', name: '%s'", ab_type_arg.c_str(), ((name) ? (name) : (""))); - } - } - else { - bot_owner->Message(Chat::White, "This command does not allow 'ActionableBot' criteria '%s'", ab_type_arg.c_str()); - } - - return ABT_None; - } - - return ab_type; - } - - 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() && !bot_grouped_player->GetRaid()) { - return nullptr; - } - - if (bot_owner->IsRaidGrouped()) { - Raid* raid = bot_grouped_player->GetRaid(); - if (!raid) { - return nullptr; - } - - uint32 raid_group = raid->GetGroup(bot_grouped_player); - if (raid_group >= MAX_RAID_GROUPS) { - return nullptr; - } - - for (const auto& m : raid->members) { - if (m.member && m.group_number == raid_group) { - if (!MyBots::IsMyBot(bot_owner, m.member->CastToBot())) { - continue; - } - - if (m.member->GetClass() != cls) { - continue; - } - - if (petless && m.member->GetPet()) { - continue; - } - - return static_cast(m.member->CastToBot()); - } - } - } - else { - 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, EQ::skills::SkillType 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 = EQ::invslot::EQUIPMENT_BEGIN; index <= EQ::invslot::EQUIPMENT_END; ++index) { - const EQ::ItemInstance* 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() != Class::Rogue && l->GetClass() != Class::Bard); }); - sbl.remove_if([bot_owner](const Bot* l) { return (l->GetClass() == Class::Rogue && l->GetLevel() < 5); }); - sbl.remove_if([bot_owner](const Bot* l) { return (l->GetClass() == Class::Bard && l->GetLevel() < 40); }); - - ActionableBots::Filter_ByHighestSkill(bot_owner, sbl, EQ::skills::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(Chat::White, "Actionable command arguments:"); - - c->Message(Chat::White, "target - selects target as single bot .. use ^command [target] or imply by empty actionable argument"); - c->Message(Chat::White, "byname [name] - selects single bot by name"); - c->Message(Chat::White, "ownergroup - selects all bots in the owner's group"); - c->Message(Chat::White, "ownerraid - selects all bots in the owner's raid"); - c->Message(Chat::White, "targetgroup - selects all bots in target's group"); - c->Message(Chat::White, "namesgroup [name] - selects all bots in name's group"); - c->Message(Chat::White, "healrotation [name] - selects all member and target bots of a heal rotation where name is a member"); - c->Message(Chat::White, "healrotationmembers [name] - selects all member bots of a heal rotation where name is a member"); - c->Message(Chat::White, "healrotationtargets [name] - selects all target bots of a heal rotation where name is a member"); - c->Message(Chat::White, "byclass - selects all bots of the chosen class"); - c->Message(Chat::White, "byrace - selects all bots of the chosen rsce"); - c->Message(Chat::White, "spawned - selects all spawned bots"); - c->Message(Chat::White, "all - selects all spawned bots .. argument use indicates en masse database updating"); - - c->Message(Chat::White, "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(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Stance); - return; - } - const int ab_mask = ActionableBots::ABM_Type1; - - std::string class_race_arg = sep->arg[1]; - bool class_race_check = false; - if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { - class_race_check = true; - } - - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, !class_race_check ? sep->arg[2] : nullptr, class_race_check ? atoi(sep->arg[2]) : 0) == 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, - fmt::format( - "Using {}.", - spells[local_entry->spell_id].name - ).c_str() - ); - } - - my_bot->UseDiscipline(local_entry->spell_id, my_bot->GetID()); - ++success_count; - - bot_iter = sbl.erase(bot_iter); - } - } - - c->Message(Chat::White, "%i of %i bots have attempted to use aggressive disciplines", success_count, candidate_count); -} - -void bot_command_apply_poison(Client *c, const Seperator *sep) -{ - if (helper_command_disabled(c, RuleB(Bots, AllowApplyPoisonCommand), "applypoison")) { - return; - } - if (helper_command_alias_fail(c, "bot_command_apply_poison", sep->arg[0], "applypoison")) { - return; - } - if (helper_is_help_or_usage(sep->arg[1])) { - - c->Message(Chat::White, "usage: %s", sep->arg[0]); - return; - } - - Bot *my_rogue_bot = nullptr; - auto t = c->GetTarget(); - - if ( - t && - t->IsBot() && - t->CastToBot()->GetBotOwnerCharacterID() == c->CharacterID() && - t->GetClass() == Class::Rogue - ) { - my_rogue_bot = t->CastToBot(); - } - - if (!my_rogue_bot) { - - c->Message(Chat::White, "You must target a rogue bot that you own to use this command!"); - return; - } - if (my_rogue_bot->GetLevel() < 18) { - - c->Message(Chat::White, "Your rogue bot must be level 18 before %s can apply poison!", (my_rogue_bot->GetGender() == Gender::Female ? "she" : "he")); - return; - } - - const auto poison_instance = c->GetInv().GetItem(EQ::invslot::slotCursor); - if (!poison_instance) { - - c->Message(Chat::White, "No item found on cursor!"); - return; - } - - auto poison_data = poison_instance->GetItem(); - if (!poison_data) { - - c->Message(Chat::White, "No data found for cursor item!"); - return; - } - - if (poison_data->ItemType == EQ::item::ItemTypePoison) { - - if ((~poison_data->Races) & GetPlayerRaceBit(my_rogue_bot->GetRace())) { - - c->Message(Chat::White, "Invalid race for weapon poison!"); - return; - } - - if (poison_data->Proc.Level2 > my_rogue_bot->GetLevel()) { - - c->Message(Chat::White, "This poison is too powerful for your intended target!"); - return; - } - - // generalized from client ApplyPoison handler - double ChanceRoll = zone->random.Real(0, 1); - uint16 poison_skill = 95 + ((my_rogue_bot->GetLevel() - 18) * 5); - if (poison_skill > 200) { - poison_skill = 200; - } - bool apply_poison_chance = (ChanceRoll < (.75 + poison_skill / 1000)); - - if (apply_poison_chance && my_rogue_bot->AddProcToWeapon(poison_data->Proc.Effect, false, (my_rogue_bot->GetDEX() / 100) + 103, POISON_PROC)) { - c->Message(Chat::White, "Successfully applied %s to %s's weapon.", poison_data->Name, my_rogue_bot->GetCleanName()); - } - else { - c->Message(Chat::White, "Failed to apply %s to %s's weapon.", poison_data->Name, my_rogue_bot->GetCleanName()); - } - - c->DeleteItemInInventory(EQ::invslot::slotCursor, 1, true); - } - else { - - c->Message(Chat::White, "Item on cursor is not a weapon poison!"); - return; - } -} - -void bot_command_apply_potion(Client* c, const Seperator* sep) -{ - if (helper_command_disabled(c, RuleB(Bots, AllowApplyPotionCommand), "applypotion")) { - return; - } - if (helper_command_alias_fail(c, "bot_command_apply_potion", sep->arg[0], "applypotion")) { - return; - } - if (helper_is_help_or_usage(sep->arg[1])) { - - c->Message(Chat::White, "usage: %s", sep->arg[0]); - return; - } - - Bot* my_bot = nullptr; - if (c->GetTarget() && c->GetTarget()->IsBot() && c->GetTarget()->CastToBot()->GetBotOwnerCharacterID() == c->CharacterID()) { - my_bot = c->GetTarget()->CastToBot(); - } - if (!my_bot) { - - c->Message(Chat::White, "You must target a bot that you own to use this command!"); - return; - } - - const auto potion_instance = c->GetInv().GetItem(EQ::invslot::slotCursor); - if (!potion_instance) { - - c->Message(Chat::White, "No item found on cursor!"); - return; - } - - auto potion_data = potion_instance->GetItem(); - if (!potion_data) { - - c->Message(Chat::White, "No data found for cursor item!"); - return; - } - - if (potion_data->ItemType == EQ::item::ItemTypePotion && potion_data->Click.Effect > 0) { - - if (RuleB(Bots, RestrictApplyPotionToRogue) && potion_data->Classes != player_class_bitmasks[Class::Rogue]) { - - c->Message(Chat::White, "This command is restricted to rogue poison potions only!"); - return; - } - if ((~potion_data->Races) & GetPlayerRaceBit(my_bot->GetRace())) { - - c->Message(Chat::White, "Invalid race for potion!"); - return; - } - if ((~potion_data->Classes) & GetPlayerClassBit(my_bot->GetClass())) { - - c->Message(Chat::White, "Invalid class for potion!"); - return; - } - - if (potion_data->Click.Level2 > my_bot->GetLevel()) { - - c->Message(Chat::White, "This potion is too powerful for your intended target!"); - return; - } - - // TODO: figure out best way to handle casting time/animation - if (my_bot->SpellFinished(potion_data->Click.Effect, my_bot, EQ::spells::CastingSlot::Item, 0)) { - c->Message(Chat::White, "Successfully applied %s to %s's buff effects.", potion_data->Name, my_bot->GetCleanName()); - } - else { - c->Message(Chat::White, "Failed to apply %s to %s's buff effects.", potion_data->Name, my_bot->GetCleanName()); - } - - c->DeleteItemInInventory(EQ::invslot::slotCursor, 1, true); - } - else { - - c->Message(Chat::White, "Item on cursor is not a potion!"); - return; - } -} - -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(Chat::White, "usage: %s [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | byclass | byrace | default: 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(Chat::White, "You must an enemy to use this command"); - return; - } - - std::string ab_arg(sep->arg[1]); - if (ab_arg.empty()) { - ab_arg = "spawned"; - } - - std::string class_race_arg(sep->arg[1]); - bool class_race_check = false; - if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { - class_race_check = true; - } - - std::list sbl; - if (ActionableBots::PopulateSBL(c, ab_arg.c_str(), sbl, ab_mask, !class_race_check ? sep->arg[2] : nullptr, class_race_check ? atoi(sep->arg[2]) : 0) == ActionableBots::ABT_None) { - return; - } - - size_t attacker_count = 0; - Bot *first_attacker = nullptr; - sbl.remove(nullptr); - for (auto bot_iter : sbl) { - - if (bot_iter->GetAppearance() != eaDead && bot_iter->GetBotStance() != EQ::constants::stancePassive) { - - if (!first_attacker) { - first_attacker = bot_iter; - } - ++attacker_count; - - bot_iter->SetAttackFlag(); - } - } - - if (attacker_count == 1 && first_attacker) { - Bot::BotGroupSay( - first_attacker, - fmt::format( - "Attacking {}.", - target_mob->GetCleanName() - ).c_str() - ); - } else { - c->Message( - Chat::White, - fmt::format( - "{} of your bots are attacking {}.", - sbl.size(), - target_mob->GetCleanName() - ).c_str() - ); - } -} - -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(Chat::White, "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(Chat::White, "Successfully bound %s to this location", target_mob->GetCleanName()); - else - c->Message(Chat::White, "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) -{ - - std::list subcommand_list; - subcommand_list.push_back("botappearance"); - subcommand_list.push_back("botcamp"); - subcommand_list.push_back("botclone"); - subcommand_list.push_back("botcreate"); - subcommand_list.push_back("botdelete"); - subcommand_list.push_back("botdetails"); - subcommand_list.push_back("botdyearmor"); - subcommand_list.push_back("botinspectmessage"); - subcommand_list.push_back("botfollowdistance"); - subcommand_list.push_back("botlist"); - subcommand_list.push_back("botoutofcombat"); - subcommand_list.push_back("botreport"); - subcommand_list.push_back("botspawn"); - subcommand_list.push_back("botstance"); - subcommand_list.push_back("botstopmeleelevel"); - subcommand_list.push_back("botsummon"); - subcommand_list.push_back("bottogglearcher"); - subcommand_list.push_back("bottogglehelm"); - subcommand_list.push_back("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_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(Chat::White, "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(Chat::White, "Your is already charmed"); - return; - } - - if (spells[local_entry->spell_id].max_value[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(Chat::White, "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(Chat::White, "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(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Stance); - return; - } - const int ab_mask = ActionableBots::ABM_Type1; - - std::string class_race_arg = sep->arg[1]; - bool class_race_check = false; - if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { - class_race_check = true; - } - - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, !class_race_check ? sep->arg[2] : nullptr, class_race_check ? atoi(sep->arg[2]) : 0) == 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_Defensive) - 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, - fmt::format( - "Using {}.", - spells[local_entry->spell_id].name - ).c_str() - ); - } - - my_bot->UseDiscipline(local_entry->spell_id, my_bot->GetID()); - ++success_count; - - bot_iter = sbl.erase(bot_iter); - } - } - - c->Message(Chat::White, "%i of %i bots have attempted to use 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(Chat::White, "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, Class::Druid); - Bot* my_wizard_bot = ActionableBots::AsGroupMember_ByClass(c, c, Class::Wizard); - helper_command_depart_list(c, my_druid_bot, my_wizard_bot, local_list, single); - return; - } - else if (destination.empty()) { - c->Message(Chat::White, "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(Chat::White, "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(Chat::White, "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(Chat::White, "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(Chat::White, "An unknown condition has occurred..."); - return; - } - - c->Message(Chat::White, "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( - Chat::White, - fmt::format( - "^{}", - alias_iter.first - ).c_str() - ); - - ++bot_command_aliases_shown; - } - c->Message(Chat::White, "%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(Chat::White, "usage: () %s ([option: reset]) [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | byclass | byrace | spawned]] ([actionable_name])", sep->arg[0]); - c->Message(Chat::White, "usage: %s chain", 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 optional_arg = sep->arg[1]; - if (!optional_arg.compare("chain")) { - - auto chain_count = helper_bot_follow_option_chain(c); - c->Message(Chat::White, "%i of your bots %s now chain following you", chain_count, (chain_count == 1 ? "is" : "are")); - - return; - } - else if (!optional_arg.compare("reset")) { - reset = true; - ab_arg = 2; - name_arg = 3; - } - else { - target_mob = ActionableTarget::VerifyFriendly(c, BCEnum::TT_Single); - if (!target_mob) { - c->Message(Chat::White, "You must a friendly mob to use this command"); - return; - } - } - - std::string class_race_arg = sep->arg[ab_arg]; - bool class_race_check = false; - if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { - class_race_check = true; - } - - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[name_arg] : nullptr, class_race_check ? atoi(sep->arg[name_arg]) : 0) == 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()); - - bot_iter->SetManualFollow(false); - } - else { - if (bot_iter == target_mob) - bot_iter->SetFollowID(c->GetID()); - else - bot_iter->SetFollowID(target_mob->GetID()); - - bot_iter->SetManualFollow(true); - } - } - else { - bot_iter->SetFollowID(0); - bot_iter->SetManualFollow(false); - } - 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(), - fmt::format( - "Following {}.", - follow_mob ? follow_mob->GetCleanName() : "no one" - ).c_str() - ); - } else { - if (reset) { - c->Message( - Chat::White, - fmt::format( - "{} of your bots are following their default assignments.", - sbl.size() - ).c_str() - ); - } else { - c->Message( - Chat::White, - fmt::format( - "{} of your bots are following {}.", - sbl.size(), - target_mob->GetCleanName() - ).c_str() - ); - } - } -} - -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(Chat::White, "usage: %s ([option: clear]) [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | byclass | byrace | spawned]] ([actionable_name])", sep->arg[0]); - return; - } - const int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_Type2); - - bool clear = false; - int ab_arg = 1; - int name_arg = 2; - - std::string clear_arg = sep->arg[1]; - if (!clear_arg.compare("clear")) { - - clear = true; - ab_arg = 2; - name_arg = 3; - } - - std::string class_race_arg = sep->arg[ab_arg]; - bool class_race_check = false; - if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { - class_race_check = true; - } - - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[name_arg] : nullptr, class_race_check ? atoi(sep->arg[name_arg]) : 0) == ActionableBots::ABT_None) { - return; - } - - sbl.remove(nullptr); - for (auto bot_iter : sbl) { - - if (clear) { - bot_iter->SetGuardFlag(false); - } - else { - bot_iter->SetGuardMode(); - } - } - - if (sbl.size() == 1) { - Bot::BotGroupSay( - sbl.front(), - fmt::format( - "{}uarding this position.", - clear ? "No longer g" : "G" - ).c_str() - ); - } else { - c->Message(Chat::White, "%i of your bots are %sguarding their positions.", sbl.size(), (clear ? "no longer " : "")); - } -} - -void bot_command_heal_rotation(Client *c, const Seperator *sep) -{ - - std::list subcommand_list; - subcommand_list.push_back("healrotationadaptivetargeting"); - subcommand_list.push_back("healrotationaddmember"); - subcommand_list.push_back("healrotationaddtarget"); - subcommand_list.push_back("healrotationadjustcritical"); - subcommand_list.push_back("healrotationadjustsafe"); - subcommand_list.push_back("healrotationcastoverride"); - subcommand_list.push_back("healrotationchangeinterval"); - subcommand_list.push_back("healrotationclearhot"); - subcommand_list.push_back("healrotationcleartargets"); - subcommand_list.push_back("healrotationcreate"); - subcommand_list.push_back("healrotationdelete"); - subcommand_list.push_back("healrotationfastheals"); - subcommand_list.push_back("healrotationlist"); - subcommand_list.push_back("healrotationremovemember"); - subcommand_list.push_back("healrotationremovetarget"); - subcommand_list.push_back("healrotationresetlimits"); - subcommand_list.push_back("healrotationsave"); - subcommand_list.push_back("healrotationsethot"); - subcommand_list.push_back("healrotationstart"); - subcommand_list.push_back("healrotationstop"); - - if (helper_command_alias_fail(c, "bot_command_heal_rotation", sep->arg[0], "healrotation")) - return; - -#if (EQDEBUG >= 12) - while (c->Admin() >= AccountStatus::GMImpossible) { - 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(Chat::White, "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( - Chat::White, - fmt::format( - "^{} - {}", - command_iter.first, - command_iter.second->desc ? command_iter.second->desc : "No Description" - ).c_str() - ); - - ++bot_commands_shown; - } - if (parse->PlayerHasQuestSub(EVENT_BOT_COMMAND)) { - int i = parse->EventPlayer(EVENT_BOT_COMMAND, c, sep->msg, 0); - if (i >= 1) { - bot_commands_shown += i; - } - } - c->Message(Chat::White, "%d bot command%s listed.", bot_commands_shown, bot_commands_shown != 1 ? "s" : ""); - c->Message(Chat::White, "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(Chat::White, "usage: %s ([option: clear]) [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | byclass | byrace | spawned]] ([actionable_name])", sep->arg[0]); - return; - } - const int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_Type2); - - bool clear = false; - int ab_arg = 1; - int name_arg = 2; - - std::string clear_arg = sep->arg[1]; - if (!clear_arg.compare("clear")) { - - clear = true; - ab_arg = 2; - name_arg = 3; - } - - std::string class_race_arg = sep->arg[ab_arg]; - bool class_race_check = false; - if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { - class_race_check = true; - } - - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[name_arg] : nullptr, class_race_check ? atoi(sep->arg[name_arg]) : 0) == ActionableBots::ABT_None) { - return; - } - - sbl.remove(nullptr); - for (auto bot_iter : sbl) { - - if (clear) { - bot_iter->SetHoldFlag(false); - } - else { - bot_iter->SetHoldMode(); - } - } - - if (sbl.size() == 1) { - Bot::BotGroupSay( - sbl.front(), - fmt::format( - "{}olding my attacks.", - clear ? "No longer h" : "H" - ).c_str() - ); - } else { - c->Message( - Chat::White, - fmt::format( - "{} of your bots are {}holding their attacks.", - sbl.size(), - clear ? "no longer " : "" - ).c_str() - ); - } -} - -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(Chat::White, "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) -{ - - std::list subcommand_list; - subcommand_list.push_back("inventorygive"); - subcommand_list.push_back("inventorylist"); - subcommand_list.push_back("inventoryremove"); - subcommand_list.push_back("inventorywindow"); - - 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(Chat::White, "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(Chat::White, "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_item_use(Client* c, const Seperator* sep) -{ - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: [%s empty] will display only bots that can use the item in an empty slot.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s byclass classID] - Example: [%s byclass 7] will display only bots that match the class that can use the item. Example is a Monk, use [^create help] for a list of class IDs.", sep->arg[0], sep->arg[0]); - c->Message(Chat::White, "usage: [%s casteronly] will display only caster bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s hybridonly] will display only hybrid bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s meleeonly] will display only melee bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s wiscasteronly] will display only Wisdom-based Caster bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s intcasteronly] will display only Intelligence-based Caster bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s plateonly] will display only Plate-wearing bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s chainonly] will display only Chain-wearing bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s leatheronly] will display only Leather-wearing bots that can use the item.", sep->arg[0]); - c->Message(Chat::White, "usage: [%s clothonly] will display only Cloth-wearing bots that can use the item.", sep->arg[0]); - return; - } - - bool empty_only = false; - int8 class_mask = 0; - bool caster_only = false; - bool hybrid_only = false; - bool melee_only = false; - bool wis_caster_only = false; - bool int_caster_only = false; - bool plate_only = false; - bool chain_only = false; - bool leather_only = false; - bool cloth_only = false; - - std::string arg1 = sep->arg[1]; - std::string arg2 = sep->arg[2]; - if (arg1.compare("empty") == 0) { - empty_only = true; - } - else if (arg1.compare("byclass") == 0) { - if (Strings::IsNumber(sep->arg[2])) { - class_mask = Strings::ToUnsignedInt(sep->arg[2]); - if (!(class_mask >= Class::Warrior && class_mask <= Class::Berserker)) { - c->Message(Chat::White, "Invalid class range, you must choose between 1 (Warrior) and 15 (Beastlord)"); - return; - } - } - } - else if (arg1.compare("casteronly") == 0) { - caster_only = true; - } - else if (arg1.compare("hybridonly") == 0) { - hybrid_only = true; - } - else if (arg1.compare("meleeonly") == 0) { - melee_only = true; - } - else if (arg1.compare("wiscasteronly") == 0) { - wis_caster_only = true; - } - else if (arg1.compare("intcasteronly") == 0) { - int_caster_only = true; - } - else if (arg1.compare("plateonly") == 0) { - plate_only = true; - } - else if (arg1.compare("chainonly") == 0) { - chain_only = true; - } - else if (arg1.compare("leatheronly") == 0) { - leather_only = true; - } - else if (arg1.compare("clothonly") == 0) { - cloth_only = true; - } - else if (!arg1.empty()) { - c->Message(Chat::White, "Please choose the correct subtype. For help use %s help.", sep->arg[0]); - return; - } - const auto item_instance = c->GetInv().GetItem(EQ::invslot::slotCursor); - if (!item_instance) { - c->Message(Chat::White, "No item found on cursor!"); - return; - } - - auto item_data = item_instance->GetItem(); - if (!item_data) { - c->Message(Chat::White, "No data found for cursor item!"); - return; - } - - if (item_data->ItemClass != EQ::item::ItemClassCommon || item_data->Slots == 0) { - c->Message(Chat::White, "'%s' is not an equipable item!", item_data->Name); - return; - } - - std::vector equipable_slot_list; - for (int16 equipable_slot = EQ::invslot::EQUIPMENT_BEGIN; equipable_slot <= EQ::invslot::EQUIPMENT_END; ++equipable_slot) { - if (item_data->Slots & (1 << equipable_slot)) { - equipable_slot_list.emplace_back(equipable_slot); - } - } - - EQ::SayLinkEngine linker; - linker.SetLinkType(EQ::saylink::SayLinkItemInst); - - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - if (class_mask) { - ActionableBots::Filter_ByClasses(c, sbl, GetPlayerClassBit(class_mask)); - } - - for (const auto& bot_iter : sbl) { - if (!bot_iter) { - continue; - } - if (caster_only && !IsCasterClass(bot_iter->GetClass())) { - continue; - } - if (hybrid_only && !IsSpellFighterClass(bot_iter->GetClass())) { - continue; - } - if (melee_only && !IsNonSpellFighterClass(bot_iter->GetClass())) { - continue; - } - if (wis_caster_only && !IsWISCasterClass(bot_iter->GetClass())) { - continue; - } - if (int_caster_only && !IsINTCasterClass(bot_iter->GetClass())) { - continue; - } - if (plate_only && !IsPlateClass(bot_iter->GetClass())) { - continue; - } - if (chain_only && !IsChainClass(bot_iter->GetClass())) { - continue; - } - if (leather_only && !IsLeatherClass(bot_iter->GetClass())) { - continue; - } - if (cloth_only && !IsClothClass(bot_iter->GetClass())) { - continue; - } - if (((~item_data->Races) & GetPlayerRaceBit(bot_iter->GetRace())) || ((~item_data->Classes) & GetPlayerClassBit(bot_iter->GetClass()))) { - continue; - } - - for (const auto& slot_iter : equipable_slot_list) { - // needs more failure criteria - this should cover the bulk for now - if (slot_iter == EQ::invslot::slotSecondary && item_data->Damage && !bot_iter->CanThisClassDualWield()) { - continue; - } - - auto equipped_item = bot_iter->GetInv()[slot_iter]; - - if (equipped_item && !empty_only) { - linker.SetItemInst(equipped_item); - - c->Message( - Chat::Say, - fmt::format( - "{} says, 'I can use that for my {} instead of my {}! Would you like to {} my {}?'", - Saylink::Silent( - fmt::format( - "^inventorygive byname {}", - bot_iter->GetCleanName() - ), - bot_iter->GetCleanName() - ), - EQ::invslot::GetInvPossessionsSlotName(slot_iter), - linker.GenerateLink(), - Saylink::Silent( - fmt::format( - "^inventoryremove {} byname {}", - slot_iter, - bot_iter->GetCleanName() - ), - "remove" - ), - linker.GenerateLink() - ).c_str() - ); - - bot_iter->DoAnim(29); - } - else if (!equipped_item) { - c->Message( - Chat::Say, - fmt::format( - "{} says, 'I can use that for my {}! Would you like to {} it to me?'", - Saylink::Silent( - fmt::format( - "^inventorygive byname {}", - bot_iter->GetCleanName() - ), - bot_iter->GetCleanName() - ), - EQ::invslot::GetInvPossessionsSlotName(slot_iter), - Saylink::Silent( - fmt::format( - "^inventorygive byname {}", - bot_iter->GetCleanName() - ), - "give" - ) - ).c_str() - ); - - bot_iter->DoAnim(29); - } - } - } -} - -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(Chat::White, "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(Chat::White, "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(Chat::White, "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_value[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(Chat::White, "usage: () %s ([group | sow])", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_MovementSpeed); - return; - } - - bool group = false; - bool sow = false; - std::string arg1 = sep->arg[1]; - if (!arg1.compare("group")) - group = true; - else if (!arg1.compare("sow")) - sow = 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 (!sow && (local_entry->group != group)) - continue; - if (sow && (local_entry->spell_id != 278)) // '278' = single-target "Spirit of Wolf" - 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_owner_option(Client *c, const Seperator *sep) -{ - if (helper_is_help_or_usage(sep->arg[1])) { - - c->Message(Chat::White, "usage: %s [option] [argument]", sep->arg[0]); - - std::string window_title = "Bot Owner Options"; - std::string window_text = - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "
Option
------
Argument
-------
Notes
-----
deathmarqueeenable | disablemarquee message on death
null(toggles)
statsupdateenable | disablereport stats on update
null(toggles)
spawnmessagesay | tell | silentspawn message into channel
class | defaultspawn with class-based message
altcombatenable | disableuse alternate ai combat behavior
null(toggles)
autodefendenable | disablebots defend owner when aggroed
null(toggles)
buffcounterenable | disablemarquee message on buff counter change
null(toggles)
monkwumessageenable | disabledisplays monk wu trigger messages
null(toggles)
currentshow current settings
"; - - c->SendPopupToClient(window_title.c_str(), window_text.c_str()); - - return; - } - - std::string owner_option(sep->arg[1]); - std::string argument(sep->arg[2]); - - if (!owner_option.compare("deathmarquee")) { - - if (!argument.compare("enable")) { - c->SetBotOption(Client::booDeathMarquee, true); - } - else if (!argument.compare("disable")) { - c->SetBotOption(Client::booDeathMarquee, false); - } - else { - c->SetBotOption(Client::booDeathMarquee, !c->GetBotOption(Client::booDeathMarquee)); - } - - database.botdb.SaveOwnerOption(c->CharacterID(), Client::booDeathMarquee, c->GetBotOption(Client::booDeathMarquee)); - - c->Message(Chat::White, "Bot 'death marquee' is now %s.", (c->GetBotOption(Client::booDeathMarquee) ? "enabled" : "disabled")); - } - else if (!owner_option.compare("statsupdate")) { - - if (!argument.compare("enable")) { - c->SetBotOption(Client::booStatsUpdate, true); - } - else if (!argument.compare("disable")) { - c->SetBotOption(Client::booStatsUpdate, false); - } - else { - c->SetBotOption(Client::booStatsUpdate, !c->GetBotOption(Client::booStatsUpdate)); - } - - database.botdb.SaveOwnerOption(c->CharacterID(), Client::booStatsUpdate, c->GetBotOption(Client::booStatsUpdate)); - - c->Message(Chat::White, "Bot 'stats update' is now %s.", (c->GetBotOption(Client::booStatsUpdate) ? "enabled" : "disabled")); - } - else if (!owner_option.compare("spawnmessage")) { - - Client::BotOwnerOption boo = Client::_booCount; - - if (!argument.compare("say")) { - - boo = Client::booSpawnMessageSay; - c->SetBotOption(Client::booSpawnMessageSay, true); - c->SetBotOption(Client::booSpawnMessageTell, false); - } - else if (!argument.compare("tell")) { - - boo = Client::booSpawnMessageSay; - c->SetBotOption(Client::booSpawnMessageSay, false); - c->SetBotOption(Client::booSpawnMessageTell, true); - } - else if (!argument.compare("silent")) { - - boo = Client::booSpawnMessageSay; - c->SetBotOption(Client::booSpawnMessageSay, false); - c->SetBotOption(Client::booSpawnMessageTell, false); - } - else if (!argument.compare("class")) { - - boo = Client::booSpawnMessageClassSpecific; - c->SetBotOption(Client::booSpawnMessageClassSpecific, true); - } - else if (!argument.compare("default")) { - - boo = Client::booSpawnMessageClassSpecific; - c->SetBotOption(Client::booSpawnMessageClassSpecific, false); - } - else { - - c->Message(Chat::White, "Owner option '%s' argument '%s' is not recognized.", owner_option.c_str(), argument.c_str()); - return; - } - - if (boo == Client::booSpawnMessageSay) { - - database.botdb.SaveOwnerOption( - c->CharacterID(), - std::pair( - Client::booSpawnMessageSay, - Client::booSpawnMessageTell - ), - std::pair( - c->GetBotOption(Client::booSpawnMessageSay), - c->GetBotOption(Client::booSpawnMessageTell) - ) - ); - } - else if (boo == Client::booSpawnMessageClassSpecific) { - - database.botdb.SaveOwnerOption( - c->CharacterID(), - Client::booSpawnMessageClassSpecific, - c->GetBotOption(Client::booSpawnMessageClassSpecific) - ); - } - else { - - c->Message(Chat::White, "Bot 'spawn message' is now ERROR."); - return; - } - - c->Message(Chat::White, "Bot 'spawn message' is now %s.", argument.c_str()); - } - else if (!owner_option.compare("altcombat")) { - - if (RuleB(Bots, AllowOwnerOptionAltCombat)) { - - if (!argument.compare("enable")) { - c->SetBotOption(Client::booAltCombat, true); - } - else if (!argument.compare("disable")) { - c->SetBotOption(Client::booAltCombat, false); - } - else { - c->SetBotOption(Client::booAltCombat, !c->GetBotOption(Client::booAltCombat)); - } - - database.botdb.SaveOwnerOption(c->CharacterID(), Client::booAltCombat, c->GetBotOption(Client::booAltCombat)); - - c->Message(Chat::White, "Bot 'alt combat' is now %s.", (c->GetBotOption(Client::booAltCombat) ? "enabled" : "disabled")); - } - else { - c->Message(Chat::White, "Bot owner option 'altcombat' is not allowed on this server."); - } - } - else if (!owner_option.compare("autodefend")) { - - if (RuleB(Bots, AllowOwnerOptionAutoDefend)) { - - if (!argument.compare("enable")) { - c->SetBotOption(Client::booAutoDefend, true); - } - else if (!argument.compare("disable")) { - c->SetBotOption(Client::booAutoDefend, false); - } - else { - c->SetBotOption(Client::booAutoDefend, !c->GetBotOption(Client::booAutoDefend)); - } - - database.botdb.SaveOwnerOption(c->CharacterID(), Client::booAutoDefend, c->GetBotOption(Client::booAutoDefend)); - - c->Message(Chat::White, "Bot 'auto defend' is now %s.", (c->GetBotOption(Client::booAutoDefend) ? "enabled" : "disabled")); - } - else { - c->Message(Chat::White, "Bot owner option 'autodefend' is not allowed on this server."); - } - } - else if (!owner_option.compare("buffcounter")) { - - if (!argument.compare("enable")) { - c->SetBotOption(Client::booBuffCounter, true); - } - else if (!argument.compare("disable")) { - c->SetBotOption(Client::booBuffCounter, false); - } - else { - c->SetBotOption(Client::booBuffCounter, !c->GetBotOption(Client::booBuffCounter)); - } - - database.botdb.SaveOwnerOption(c->CharacterID(), Client::booBuffCounter, c->GetBotOption(Client::booBuffCounter)); - - c->Message(Chat::White, "Bot 'buff counter' is now %s.", (c->GetBotOption(Client::booBuffCounter) ? "enabled" : "disabled")); - } - else if (!owner_option.compare("monkwumessage")) { - - if (!argument.compare("enable")) { - c->SetBotOption(Client::booMonkWuMessage, true); - } - else if (!argument.compare("disable")) { - c->SetBotOption(Client::booMonkWuMessage, false); - } - else { - c->SetBotOption(Client::booMonkWuMessage, !c->GetBotOption(Client::booMonkWuMessage)); - } - - database.botdb.SaveOwnerOption(c->CharacterID(), Client::booMonkWuMessage, c->GetBotOption(Client::booMonkWuMessage)); - - c->Message( - Chat::White, - "Bot 'monk wu message' is now %s.", - (c->GetBotOption(Client::booMonkWuMessage) ? "enabled" : "disabled") - ); - } - else if (!owner_option.compare("current")) { - - std::string window_title = "Current Bot Owner Options Settings"; - std::string window_text = fmt::format( - "" - "" - "" - "" - "" - "" "" "" "" - "" "" "" "" - "" "" "" "" - "" "" "" "" - "" "" "" "" - "" "" "" "" - "" "" "" "" - "" "" "" "" - "
Option
------
Argument
-------
deathmarquee{}
statsupdate{}
spawnmessage{}
spawnmessage{}
altcombat{}
autodefend{}
buffcounter{}
monkwumessage{}
", - (c->GetBotOption(Client::booDeathMarquee) ? "enabled" : "disabled"), - (c->GetBotOption(Client::booStatsUpdate) ? "enabled" : "disabled"), - (c->GetBotOption(Client::booSpawnMessageSay) ? "say" : (c->GetBotOption(Client::booSpawnMessageTell) ? "tell" : "silent")), - (c->GetBotOption(Client::booSpawnMessageClassSpecific) ? "class" : "default"), - (RuleB(Bots, AllowOwnerOptionAltCombat) ? (c->GetBotOption(Client::booAltCombat) ? "enabled" : "disabled") : "restricted"), - (RuleB(Bots, AllowOwnerOptionAutoDefend) ? (c->GetBotOption(Client::booAutoDefend) ? "enabled" : "disabled") : "restricted"), - (c->GetBotOption(Client::booBuffCounter) ? "enabled" : "disabled"), - (c->GetBotOption(Client::booMonkWuMessage) ? "enabled" : "disabled") - ); - - c->SendPopupToClient(window_title.c_str(), window_text.c_str()); - } - else { - c->Message(Chat::White, "Owner option '%s' is not recognized.", owner_option.c_str()); - } -} - -void bot_command_pet(Client *c, const Seperator *sep) -{ - - std::list subcommand_list; - subcommand_list.push_back("petgetlost"); - subcommand_list.push_back("petremove"); - subcommand_list.push_back("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(Chat::White, "usage: %s", sep->arg[0]); - c->Message(Chat::White, "requires one of the following bot classes:"); - c->Message(Chat::White, "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(Chat::White, "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(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(Chat::White, "%i door%s attempted - %i door%s successful", door_count, ((door_count != 1) ? ("s") : ("")), open_count, ((open_count != 1) ? ("s") : (""))); -} - -void bot_command_precombat(Client* c, const Seperator* sep) -{ - if (helper_command_alias_fail(c, "bot_command_precombat", sep->arg[0], "precombat")) { - return; - } - if (helper_is_help_or_usage(sep->arg[1])) { - - c->Message(Chat::White, "usage: %s ([set | clear])", sep->arg[0]); - return; - } - - if (!c->GetTarget() || !c->IsAttackAllowed(c->GetTarget())) { - - c->Message(Chat::White, "This command requires an attackable target."); - return; - } - - std::string argument(sep->arg[1]); - - if (!argument.compare("set")) { - c->SetBotPrecombat(true); - } - else if (!argument.compare("clear")) { - c->SetBotPrecombat(false); - } - else { - c->SetBotPrecombat(!c->GetBotPrecombat()); - } - - c->Message(Chat::White, "Precombat flag is now %s.", (c->GetBotPrecombat() ? "set" : "clear")); -} - -// TODO: Rework to allow owner specificed criteria for puller -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(Chat::White, "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(Chat::White, "Your current target is not attackable!"); - return; - } - - if (target_mob->IsNPC() && target_mob->GetHateList().size()) { - - c->Message(Chat::White, "Your current target is already engaged!"); - return; - } - - Bot* bot_puller = nullptr; - for (auto bot_iter : sbl) { - - if (bot_iter->GetAppearance() == eaDead || bot_iter->GetBotStance() == EQ::constants::stancePassive) { - continue; - } - - switch (bot_iter->GetClass()) { - case Class::Rogue: - case Class::Monk: - case Class::Bard: - case Class::Ranger: - bot_puller = bot_iter; - break; - case Class::Warrior: - case Class::ShadowKnight: - case Class::Paladin: - case Class::Berserker: - case Class::Beastlord: - if (!bot_puller) { - - bot_puller = bot_iter; - continue; - } - - switch (bot_puller->GetClass()) { - case Class::Druid: - case Class::Shaman: - case Class::Cleric: - case Class::Wizard: - case Class::Necromancer: - case Class::Magician: - case Class::Enchanter: - bot_puller = bot_iter; - default: - continue; - } - - continue; - case Class::Druid: - case Class::Shaman: - case Class::Cleric: - if (!bot_puller) { - - bot_puller = bot_iter; - continue; - } - - switch (bot_puller->GetClass()) { - case Class::Wizard: - case Class::Necromancer: - case Class::Magician: - case Class::Enchanter: - bot_puller = bot_iter; - default: - continue; - } - - continue; - case Class::Wizard: - case Class::Necromancer: - case Class::Magician: - case Class::Enchanter: - if (!bot_puller) { - bot_puller = bot_iter; - } - - continue; - default: - continue; - } - - - bot_puller = bot_iter; - - break; - } - - if (bot_puller) { - bot_puller->SetPullFlag(); - } - - 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(Chat::White, "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(Chat::White, "%i of your bots %s released.", 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(Chat::White, "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(Chat::White, "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(Chat::White, "usage: %s ([option: aoe])", sep->arg[0]); - c->Message(Chat::White, "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(Chat::White, "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(Chat::White, "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(Chat::White, "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(Chat::White, "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(Chat::White, "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(Chat::White, "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_value[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_suspend(Client *c, const Seperator *sep) -{ - if (helper_command_alias_fail(c, "bot_command_suspend", sep->arg[0], "suspend")) { - return; - } - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "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(Chat::White, "%i of your bots %s suspended.", sbl.size(), ((sbl.size() != 1) ? ("are") : ("is"))); -} - -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(Chat::White, "usage: %s ([option: on | off]) ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | 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::string class_race_arg = sep->arg[ab_arg]; - bool class_race_check = false; - if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { - class_race_check = true; - } - - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[(ab_arg + 1)] : nullptr, class_race_check ? atoi(sep->arg[(ab_arg + 1)]) : 0) == ActionableBots::ABT_None) { - return; - } - sbl.remove(nullptr); - - int taunting_count = 0; - for (auto bot_iter : sbl) { - if (!bot_iter->GetSkill(EQ::skills::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, - fmt::format( - "I am {} taunting.", - bot_iter->IsTaunting() ? "now" : "no longer" - ).c_str() - ); - } - - ++taunting_count; - } - - for (auto bot_iter : sbl) { - if (!bot_iter->HasPet()) { - continue; - } - - if (!bot_iter->GetPet()->GetSkill(EQ::skills::SkillTaunt)) { - continue; - } - - if (toggle_taunt) { - bot_iter->GetPet()->CastToNPC()->SetTaunting(!bot_iter->GetPet()->CastToNPC()->IsTaunting()); - } else { - bot_iter->GetPet()->CastToNPC()->SetTaunting(taunt_state); - } - - if (sbl.size() == 1) { - Bot::BotGroupSay( - bot_iter, - fmt::format( - "My Pet is {} taunting.", - bot_iter->GetPet()->CastToNPC()->IsTaunting() ? "now" : "no longer" - ).c_str() - ); - } - - ++taunting_count; - } - - if (taunting_count) { - if (toggle_taunt) { - c->Message( - Chat::White, - fmt::format( - "{} of your bots and their pets {} toggled their taunting state", - taunting_count, - taunting_count != 1 ? "have" : "has" - ).c_str() - ); - } else { - c->Message( - Chat::White, - fmt::format( - "{} of your bots and their pets {} {} taunting.", - taunting_count, - taunting_count != 1 ? "have" : "has", - taunt_state ? "started" : "stopped" - ).c_str() - ); - } - } - else { - c->Message(Chat::White, "None of your bots are capable of taunting"); - } -} - -void bot_command_timer(Client* c, const Seperator* sep) -{ - if (helper_command_alias_fail(c, "bot_command_timer", sep->arg[0], "timer")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [clear | has | set] [disc | item | spell] [timer ID | item ID | spell ID | all] [optional ms for set] [actionable].", sep->arg[0]); - c->Message(Chat::White, "When setting, you can leave the value blank to use the default for the item or specify a value in ms to set the timer to."); - c->Message(Chat::White, "Returns or sets the provided timer(s) for the selected bot(s) or clears the selected timer(s) for the selected bot(s)."); - return; - } - - const int ab_mask = ActionableBots::ABM_Type1; - - std::string arg1 = sep->arg[1]; - std::string arg2 = sep->arg[2]; - std::string arg3 = sep->arg[3]; - int ab_arg = 4; - bool clear = false; - bool has = false; - bool set = false; - bool disc = false; - bool item = false; - bool spell = false; - uint32 timer_id = 0; - uint32 timer_value = 0; - bool all = false; - - if (!arg1.compare("clear")) { - clear = true; - } - else if (!arg1.compare("has")) { - has = true; - } - else if (!arg1.compare("set")) { - set = true; - } - else { - c->Message(Chat::White, "Incorrect argument, use %s help for a list of options.", sep->arg[0]); - return; - } - - if (!arg2.compare("disc")) { - disc = true; - } - else if (!arg2.compare("item")) { - item = true; - } - else if (!arg2.compare("spell")) { - spell = true; - } - else { - c->Message(Chat::White, "Incorrect timer type, use %s help for a list of options.", sep->arg[0]); - return; - } - - if (sep->IsNumber(3)) { - timer_id = atoi(sep->arg[3]); - if (timer_id < 0) { - c->Message(Chat::White, "You cannot use negative numbers."); - return; - } - } - else if (!arg3.compare("all")) { - if (has || set) { - c->Message(Chat::White, "You can only use 'all' for clearing timers."); - return; - } - - all = true; - } - else { - c->Message(Chat::White, "Incorrect ID option, use %s help for a list of options.", sep->arg[0]); - return; - } - - if (set) { - if (sep->IsNumber(4)) { - ab_arg = 5; - timer_value = atoi(sep->arg[4]); - if (timer_value <= 0) { - c->Message(Chat::White, "You cannot use 0 or negative numbers."); - return; - } - } - } - - std::string class_race_arg = sep->arg[ab_arg]; - bool class_race_check = false; - if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { - class_race_check = true; - } - - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { - return; - } - sbl.remove(nullptr); - - for (auto my_bot : sbl) { - bool found = false; - - if (clear) { - c->Message( - Chat::White, - fmt::format( - "{} says, 'Clearing {} timer{}'", - my_bot->GetCleanName(), - disc ? "Discipline" : item ? "Item" : "Spell", - (all ? "s." : ".") - ).c_str() - ); - - if (disc) { - my_bot->ClearDisciplineReuseTimer(timer_id); - } - else if (item) { - my_bot->ClearItemReuseTimer(timer_id); - } - else if (spell) { - my_bot->ClearSpellRecastTimer(timer_id); - } - } - else if (has) { - uint32 remaining_time; - std::string time_string = ""; - - if (disc) { - if (!my_bot->CheckDisciplineReuseTimer(timer_id)) { - remaining_time = my_bot->GetDisciplineReuseRemainingTime(timer_id) / 1000; - time_string = Strings::SecondsToTime(remaining_time); - found = true; - } - } - else if (item) { - if (!my_bot->CheckItemReuseTimer(timer_id)) { - remaining_time = my_bot->GetItemReuseRemainingTime(timer_id) / 1000; - time_string = Strings::SecondsToTime(remaining_time); - found = true; - } - } - else if (spell) { - if (!my_bot->CheckSpellRecastTimer(timer_id)) { - remaining_time = my_bot->GetSpellRecastRemainingTime(timer_id) / 1000; - time_string = Strings::SecondsToTime(remaining_time); - found = true; - } - } - - c->Message( - Chat::White, - fmt::format( - "{} says, 'I {}{}{}'", - my_bot->GetCleanName(), - (!found ? " do not have that timer currently" : " have "), - (!found ? "" : time_string), - (!found ? "." : " remaining.") - ).c_str() - ); - } - else if (set) { - c->Message( - Chat::White, - fmt::format( - "{} says, 'Setting {} timer{} for {} to {}.'", - my_bot->GetCleanName(), - disc ? "Discipline" : item ? "Item" : "Spell", - (all ? "s" : ""), - timer_id, - timer_value ? std::to_string(timer_value) : "the default value" - ).c_str() - ); - - if (disc) { - my_bot->ClearDisciplineReuseTimer(timer_id); - my_bot->SetDisciplineReuseTimer(timer_id, timer_value); - } - else if (item) { - my_bot->ClearItemReuseTimer(timer_id); - my_bot->SetItemReuseTimer(timer_id, timer_value); - } - else if (spell) { - my_bot->ClearSpellRecastTimer(timer_id); - my_bot->SetSpellRecastTimer(timer_id, timer_value); - } - } - } -} - -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(Chat::White, "usage: %s (Ranger: [option=all: all | rare | local])", sep->arg[0]); - c->Message(Chat::White, "requires one of the following bot classes:"); - c->Message(Chat::White, "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_bitmasks[Class::Ranger] | player_class_bitmasks[Class::Druid] | player_class_bitmasks[Class::Bard]); - ActionableBots::Filter_ByClasses(c, sbl, class_mask); - - Bot* my_bot = ActionableBots::AsSpawned_ByMinLevelAndClass(c, sbl, 1, Class::Ranger); - if (tracking_scope.empty()) { - if (!my_bot) - my_bot = ActionableBots::AsSpawned_ByMinLevelAndClass(c, sbl, 20, Class::Druid); - if (!my_bot) - my_bot = ActionableBots::AsSpawned_ByMinLevelAndClass(c, sbl, 35, Class::Bard); - } - if (!my_bot) { - c->Message(Chat::White, "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 Class::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 Class::Druid: - base_distance = 30; - tracking_msg = "Local tracking..."; - break; - case Class::Bard: - base_distance = 20; - tracking_msg = "Near tracking..."; - break; - default: - return; - } - if (!base_distance) { - c->Message(Chat::White, "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(Chat::White, "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) -{ - - std::list subcommand_list; - subcommand_list.push_back("botbeardcolor"); - subcommand_list.push_back("botbeardstyle"); - subcommand_list.push_back("botdetails"); - subcommand_list.push_back("boteyes"); - subcommand_list.push_back("botface"); - subcommand_list.push_back("bothaircolor"); - subcommand_list.push_back("bothairstyle"); - subcommand_list.push_back("botheritage"); - subcommand_list.push_back("bottattoo"); - subcommand_list.push_back("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(Chat::White, "usage: %s [value: 0-n] (Dwarves or male bots only)", sep->arg[0]); - c->Message(Chat::White, "note: Actual limit is filter-based"); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must a bot that you own to use this command"); - return; - } - - if (!sep->IsNumber(1)) { - c->Message(Chat::White, "A numeric [value] is required to use this command"); - return; - } - - uint8 uvalue = Strings::ToInt(sep->arg[1]); - - auto fail_type = BCEnum::AFT_None; - if (my_bot->GetGender() != Gender::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(Chat::White, "usage: %s [value: 0-n] (Dwarves or male bots only)", sep->arg[0]); - c->Message(Chat::White, "note: Actual limit is filter-based"); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must a bot that you own to use this command"); - return; - } - - if (!sep->IsNumber(1)) { - c->Message(Chat::White, "A numeric [value] is required to use this command"); - return; - } - - uint8 uvalue = Strings::ToInt(sep->arg[1]); - - auto fail_type = BCEnum::AFT_None; - if (my_bot->GetGender() != Gender::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(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); - return; - } - const int ab_mask = ActionableBots::ABM_Type1; - - std::string class_race_arg = sep->arg[1]; - bool class_race_check = false; - if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { - class_race_check = true; - } - - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, !class_race_check ? sep->arg[2] : nullptr, class_race_check ? atoi(sep->arg[2]) : 0) == 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( - Chat::White, - fmt::format( - "Usage: {} [clone_name]", - sep->arg[0] - ).c_str() - ); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must target a bot that you own to use this command!"); - return; - } - - if (!my_bot->GetBotID()) { - c->Message( - Chat::White, - fmt::format( - "An unknown error has occured with {} (Bot ID {}).", - my_bot->GetCleanName(), - my_bot->GetBotID() - ).c_str() - ); - LogCommands( - "bot_command_clone(): - Error: Active bot reported invalid ID (BotName: [{}], BotID: [{}], OwnerName: [{}], OwnerID: [{}], AcctName: [{}], AcctID: [{}])", - 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(Chat::White, "You must name your bot clone."); - return; - } - - std::string bot_name = sep->arg[1]; - - if (!Bot::IsValidName(bot_name)) { - c->Message( - Chat::White, - fmt::format( - "'{}' is an invalid name. You may only use characters 'A-Z', 'a-z' and '_'.", - bot_name - ).c_str() - ); - return; - } - - bool available_flag = false; - if (!database.botdb.QueryNameAvailablity(bot_name, available_flag)) { - c->Message( - Chat::White, - fmt::format( - "Failed to query name availability for '{}'.", - bot_name - ).c_str() - ); - return; - } - - if (!available_flag) { - c->Message( - Chat::White, - fmt::format( - "The name '{}' is already being used. Please choose a different name.", - bot_name - ).c_str() - ); - return; - } - - auto bot_creation_limit = c->GetBotCreationLimit(); - auto bot_creation_limit_class = c->GetBotCreationLimit(my_bot->GetClass()); - - uint32 bot_count = 0; - uint32 bot_class_count = 0; - if (!database.botdb.QueryBotCount(c->CharacterID(), my_bot->GetClass(), bot_count, bot_class_count)) { - c->Message(Chat::White, "Failed to query bot count."); - return; - } - - if (bot_creation_limit >= 0 && bot_count >= bot_creation_limit) { - std::string message; - - if (bot_creation_limit) { - message = fmt::format( - "You have reached the maximum limit of {} bot{}.", - bot_creation_limit, - bot_creation_limit != 1 ? "s" : "" - ); - } else { - message = "You cannot create any bots."; - } - - c->Message(Chat::White, message.c_str()); - return; - } - - if (bot_creation_limit_class >= 0 && bot_class_count >= bot_creation_limit_class) { - std::string message; - - if (bot_creation_limit_class) { - message = fmt::format( - "You cannot create anymore than {} {} bot{}.", - bot_creation_limit_class, - GetClassIDName(my_bot->GetClass()), - bot_creation_limit_class != 1 ? "s" : "" - ); - } else { - message = fmt::format( - "You cannot create any {} bots.", - GetClassIDName(my_bot->GetClass()) - ); - } - - c->Message(Chat::White, message.c_str()); - return; - } - - uint32 clone_id = 0; - if (!database.botdb.CreateCloneBot(my_bot->GetBotID(), bot_name, clone_id) || !clone_id) { - c->Message( - Chat::White, - fmt::format( - "Failed to create clone bot '{}'.", - bot_name - ).c_str() - ); - return; - } - - int clone_stance = EQ::constants::stancePassive; - if (!database.botdb.LoadStance(my_bot->GetBotID(), clone_stance)) { - c->Message( - Chat::White, - fmt::format( - "Failed to load stance from '{}'.", - my_bot->GetCleanName() - ).c_str() - ); - } - - if (!database.botdb.SaveStance(clone_id, clone_stance)) { - c->Message( - Chat::White, - fmt::format( - "Failed to save stance for clone '{}'.", - bot_name - ).c_str() - ); - } - - if (!database.botdb.CreateCloneBotInventory(my_bot->GetBotID(), clone_id)) { - c->Message( - Chat::White, - fmt::format( - "Failed to create clone bot inventory for clone '{}'.", - bot_name - ).c_str() - ); - } - - c->Message( - Chat::White, - fmt::format( - "Bot Cloned | From: {} To: {}", - my_bot->GetCleanName(), - bot_name - ).c_str() - ); -} - -void bot_command_view_combos(Client *c, const Seperator *sep) -{ - const std::string class_substrs[17] = { - "", - "WAR", "CLR", "PAL", "RNG", - "SHD", "DRU", "MNK", "BRD", - "ROG", "SHM", "NEC", "WIZ", - "MAG", "ENC", "BST", "BER" - }; - - const std::string race_substrs[17] = { - "", - "HUM", "BAR", "ERU", "ELF", - "HIE", "DEF", "HEF", "DWF", - "TRL", "OGR", "HFL", "GNM", - "IKS", "VAH", "FRG", "DRK" - }; - - const uint16 race_values[17] = { - Race::Doug, - Race::Human, Race::Barbarian, Race::Erudite, Race::WoodElf, - Race::HighElf, Race::DarkElf, Race::HalfElf, Race::Dwarf, - Race::Troll, Race::Ogre, Race::Halfling, Race::Gnome, - Race::Iksar, Race::VahShir, Race::Froglok2, Race::Drakkin - }; - - if (helper_command_alias_fail(c, "bot_command_view_combos", sep->arg[0], "viewcombos")) { - return; - } - - if (helper_is_help_or_usage(sep->arg[1])) { - std::string window_text; - std::string message_separator = " "; - c->Message(Chat::White, fmt::format("Usage: {} [Race]", sep->arg[0]).c_str()); - - window_text.append(""); - - for (int race_id = 0; race_id <= 15; ++race_id) { - window_text.append(message_separator); - window_text.append( - fmt::format( - "{} ({})", - race_substrs[race_id + 1], - race_values[race_id + 1] - ) - ); - - message_separator = ", "; - } - c->SendPopupToClient("Bot Races", window_text.c_str()); - return; - } - - if (sep->arg[1][0] == '\0' || !sep->IsNumber(1)) { - c->Message(Chat::White, "Invalid Race!"); - return; - } - - const uint16 bot_race = static_cast(Strings::ToUnsignedInt(sep->arg[1])); - const std::string race_name = GetRaceIDName(bot_race); - - if (!IsPlayerRace(bot_race)) { - c->Message( - Chat::White, - fmt::format( - "{} ({}) is not a race bots can use.", - race_name, - bot_race - ).c_str() - ); - return; - } - - const auto classes_bitmask = database.botdb.GetRaceClassBitmask(bot_race); - - std::string window_text; - std::string message_separator = " "; - - window_text.append(""); - - const int object_max = 4; - auto object_count = 0; - - for (int class_id = 0; class_id <= 15; ++class_id) { - if (classes_bitmask & GetPlayerClassBit(class_id)) { - window_text.append(message_separator); - - if (object_count >= object_max) { - window_text.append(DialogueWindow::Break()); - object_count = 0; - } - - window_text.append( - fmt::format( - "{} ({})", - class_substrs[class_id], - class_id - ) - ); - - ++object_count; - message_separator = ", "; - } - } - - c->SendPopupToClient( - fmt::format( - "Bot Classes for {} ({})", - race_name, - bot_race - ).c_str(), - window_text.c_str() - ); -} - -void bot_subcommand_bot_create(Client *c, const Seperator *sep) -{ - const std::string class_substrs[17] = { - "", - "WAR", "CLR", "PAL", "RNG", - "SHD", "DRU", "MNK", "BRD", - "ROG", "SHM", "NEC", "WIZ", - "MAG", "ENC", "BST", "BER" - }; - - const std::string race_substrs[17] = { - "", - "HUM", "BAR", "ERU", "ELF", - "HIE", "DEF", "HEF", "DWF", - "TRL", "OGR", "HFL", "GNM", - "IKS", "VAH", "FRG", "DRK" - }; - - const uint16 race_values[17] = { - Race::Doug, - Race::Human, Race::Barbarian, Race::Erudite, Race::WoodElf, - Race::HighElf, Race::DarkElf, Race::HalfElf, Race::Dwarf, - Race::Troll, Race::Ogre, Race::Halfling, Race::Gnome, - Race::Iksar, Race::VahShir, Race::Froglok2, Race::Drakkin - }; - - const std::string gender_substrs[2] = { - "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( - Chat::White, - fmt::format( - "Usage: {} [Name] [Class] [Race] [Gender]", - sep->arg[0] - ).c_str() - ); - - std::string window_text; - std::string message_separator; - int object_count = 0; - const int object_max = 4; - - window_text.append( - fmt::format( - "Classes{}", - DialogueWindow::Break() - ) - ); - - message_separator = " "; - object_count = 0; - for (int i = 0; i <= 15; ++i) { - window_text.append(message_separator); - - if (object_count >= object_max) { - window_text.append(DialogueWindow::Break()); - object_count = 0; - } - - window_text.append( - fmt::format("{} ({})", - class_substrs[i + 1], - (i + 1) - ) - ); - - ++object_count; - message_separator = ", "; - } - - window_text.append(DialogueWindow::Break(2)); - - window_text.append( - fmt::format( - "Races{}", - DialogueWindow::Break() - ) - ); - - message_separator = " "; - object_count = 0; - for (int i = 0; i <= 15; ++i) { - window_text.append(message_separator); - - if (object_count >= object_max) { - window_text.append(DialogueWindow::Break()); - object_count = 0; - } - - window_text.append( - fmt::format("{} ({})", - race_substrs[i + 1], - race_values[i + 1] - ) - ); - - ++object_count; - message_separator = ", "; - } - - window_text.append(DialogueWindow::Break(2)); - - window_text.append( - fmt::format( - "Genders{}", - DialogueWindow::Break() - ) - ); - - message_separator = " "; - for (int i = 0; i <= 1; ++i) { - window_text.append(message_separator); - - window_text.append( - fmt::format("{} ({})", - gender_substrs[i], - i - ) - ); - - message_separator = ", "; - } - - c->SendPopupToClient("Bot Creation Options", window_text.c_str()); - - return; - } - - const auto arguments = sep->argnum; - if (!arguments || sep->IsNumber(1)) { - c->Message(Chat::White, "You must name your bot!"); - return; - } - - std::string bot_name = sep->arg[1]; - bot_name = Strings::UcFirst(bot_name); - - if (arguments < 2 || !sep->IsNumber(2)) { - c->Message(Chat::White, "Invalid class!"); - return; - } - - auto bot_class = static_cast(Strings::ToUnsignedInt(sep->arg[2])); - - if (arguments < 3 || !sep->IsNumber(3)) { - c->Message(Chat::White, "Invalid race!"); - return; - } - - auto bot_race = static_cast(Strings::ToUnsignedInt(sep->arg[3])); - - if (arguments < 4) { - c->Message(Chat::White, "Invalid gender!"); - return; - } - - auto bot_gender = Gender::Male; - - if (sep->IsNumber(4)) { - bot_gender = static_cast(Strings::ToUnsignedInt(sep->arg[4])); - if (bot_gender == Gender::Neuter) { - bot_gender = Gender::Male; - } - } else { - if (!strcasecmp(sep->arg[4], "m") || !strcasecmp(sep->arg[4], "male")) { - bot_gender = Gender::Male; - } else if (!strcasecmp(sep->arg[4], "f") || !strcasecmp(sep->arg[4], "female")) { - bot_gender = Gender::Female; - } - } - - 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(Chat::White, "usage: %s", sep->arg[0]); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must a bot that you own to use this command"); - return; - } - - if (!my_bot->DeleteBot()) { - c->Message(Chat::White, "Failed to delete '%s' due to database error", my_bot->GetCleanName()); - return; - } - - auto bid = my_bot->GetBotID(); - std::string bot_name = my_bot->GetCleanName(); - - my_bot->Camp(false); - - c->Message(Chat::White, "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(Chat::White, "usage: %s [value: 0-n] (Drakkin bots only)", sep->arg[0]); - c->Message(Chat::White, "note: Actual limit is filter-based"); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must a bot that you own to use this command"); - return; - } - - if (!sep->IsNumber(1)) { - c->Message(Chat::White, "A numeric [value] is required to use this command"); - return; - } - - uint32 uvalue = Strings::ToInt(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 material_slot_message = fmt::format( - "Material Slots: * (All), {} (Head), {} (Chest), {} (Arms), {} (Wrists), {} (Hands), {} (Legs), {} (Feet)", - EQ::textures::armorHead, - EQ::textures::armorChest, - EQ::textures::armorArms, - EQ::textures::armorWrist, - EQ::textures::armorHands, - EQ::textures::armorLegs, - EQ::textures::armorFeet - ); - - 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]) || !sep->arg[1] || (sep->arg[1] && !Strings::IsNumber(sep->arg[1]))) { - c->Message( - Chat::White, - fmt::format( - "Usage: {} [Material Slot] [Red: 0-255] [Green: 0-255] [Blue: 0-255] ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", - sep->arg[0] - ).c_str() - ); - c->Message(Chat::White, material_slot_message.c_str()); - return; - } - const int ab_mask = ActionableBots::ABM_NoFilter; - - uint8 material_slot = EQ::textures::materialInvalid; - int16 slot_id = INVALID_INDEX; - - bool dye_all = (sep->arg[1][0] == '*'); - if (!dye_all) { - material_slot = Strings::ToInt(sep->arg[1]); - slot_id = EQ::InventoryProfile::CalcSlotFromMaterial(material_slot); - - if (!sep->IsNumber(1) || slot_id == INVALID_INDEX || material_slot > EQ::textures::LastTintableTexture) { - c->Message(Chat::White, "Valid material slots for this command are:"); - c->Message(Chat::White, material_slot_message.c_str()); - return; - } - } - - if (!sep->IsNumber(2)) { - c->Message(Chat::White, "Valid Red values for this command are 0 to 255."); - return; - } - - uint32 red_value = Strings::ToUnsignedInt(sep->arg[2]); - if (red_value > 255) { - red_value = 255; - } - - if (!sep->IsNumber(3)) { - c->Message(Chat::White, "Valid Green values for this command are 0 to 255."); - return; - } - - uint32 green_value = Strings::ToUnsignedInt(sep->arg[3]); - if (green_value > 255) { - green_value = 255; - } - - if (!sep->IsNumber(4)) { - c->Message(Chat::White, "Valid Blue values for this command are 0 to 255."); - return; - } - - uint32 blue_value = Strings::ToUnsignedInt(sep->arg[4]); - if (blue_value > 255) { - blue_value = 255; - } - - uint32 rgb_value = ((uint32)red_value << 16) | ((uint32)green_value << 8) | ((uint32)blue_value); - - 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( - Chat::White, - fmt::format( - "Failed to change armor color for {} due to unknown cause.", - bot_iter->GetCleanName() - ).c_str() - ); - return; - } - } - - if (ab_type == ActionableBots::ABT_All) { - if (dye_all) { - database.botdb.SaveAllArmorColors(c->CharacterID(), rgb_value); - } else { - database.botdb.SaveAllArmorColorBySlot(c->CharacterID(), slot_id, rgb_value); - } - } -} - -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(Chat::White, "usage: %s [value:0-n] ([option: left | right])", sep->arg[0]); - c->Message(Chat::White, "usage: %s [value: 0-n]", sep->arg[0]); - c->Message(Chat::White, "note: Actual limit is filter-based"); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must a bot that you own to use this command"); - return; - } - - if (!sep->IsNumber(1)) { - c->Message(Chat::White, "A numeric [value] is required to use this command"); - return; - } - - uint8 uvalue = Strings::ToInt(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(Chat::White, "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(Chat::White, "usage: %s [value: 0-n]", sep->arg[0]); - c->Message(Chat::White, "note: Actual limit is filter-based"); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must a bot that you own to use this command"); - return; - } - - if (!sep->IsNumber(1)) { - c->Message(Chat::White, "A numeric [value] is required to use this command"); - return; - } - - uint8 uvalue = Strings::ToInt(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(Chat::White, "usage: %s [set] [distance] ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); - c->Message(Chat::White, "usage: %s [clear] ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); - return; - } - const int ab_mask = ActionableBots::ABM_NoFilter; - - uint32 bfd = BOT_FOLLOW_DISTANCE_DEFAULT; - bool set_flag = false; - int ab_arg = 2; - - if (!strcasecmp(sep->arg[1], "set")) { - if (!sep->IsNumber(2)) { - c->Message(Chat::White, "A numeric [distance] is required to use this command"); - return; - } - - bfd = Strings::ToInt(sep->arg[2]); - if (bfd < 1) - bfd = 1; - if (bfd > BOT_FOLLOW_DISTANCE_DEFAULT_MAX) - bfd = BOT_FOLLOW_DISTANCE_DEFAULT_MAX; - set_flag = true; - ab_arg = 3; - } - else if (strcasecmp(sep->arg[1], "clear")) { - c->Message(Chat::White, "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 && !database.botdb.SaveFollowDistance(bot_iter->GetBotID(), bfd)) { - return; - } - - ++bot_count; - } - - if (ab_type == ActionableBots::ABT_All) { - if (!database.botdb.SaveAllFollowDistances(c->CharacterID(), bfd)) { - return; - } - - c->Message(Chat::White, "%s all of your bot follow distances", set_flag ? "Set" : "Cleared"); - } - else { - c->Message(Chat::White, "%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(Chat::White, "usage: %s [value: 0-n]", sep->arg[0]); - c->Message(Chat::White, "note: Actual limit is filter-based"); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must a bot that you own to use this command"); - return; - } - - if (!sep->IsNumber(1)) { - c->Message(Chat::White, "A numeric [value] is required to use this command"); - return; - } - - uint8 uvalue = Strings::ToInt(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(Chat::White, "usage: %s [value: 0-n]", sep->arg[0]); - c->Message(Chat::White, "note: Actual limit is filter-based"); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must a bot that you own to use this command"); - return; - } - - if (!sep->IsNumber(1)) { - c->Message(Chat::White, "A numeric [value] is required to use this command"); - return; - } - - uint8 uvalue = Strings::ToInt(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(Chat::White, "usage: %s [value: 0-n] (Drakkin bots only)", sep->arg[0]); - c->Message(Chat::White, "note: Actual limit is filter-based"); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must a bot that you own to use this command"); - return; - } - - if (!sep->IsNumber(1)) { - c->Message(Chat::White, "A numeric [value] is required to use this command"); - return; - } - - uint32 uvalue = Strings::ToInt(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(Chat::White, "usage: %s [set | clear] ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); - c->Message(Chat::White, "Notes:"); - if (c->ClientVersion() >= EQ::versions::ClientVersion::SoF) { - c->Message(Chat::White, "- Self-inspect and type your bot's inspect message"); - c->Message(Chat::White, "- Close the self-inspect window to update the server"); - c->Message(Chat::White, "- Type '%s set' to set the bot's inspect message", sep->arg[0]); - } - else { - c->Message(Chat::White, "- Self-inspect and type your bot's inspect message"); - c->Message(Chat::White, "- Close the self-inspect window"); - c->Message(Chat::White, "- Self-inspect again to update the server"); - c->Message(Chat::White, "- 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(Chat::White, "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 client_message_struct = &c->GetInspectMessage(); - - int bot_count = 0; - for (auto bot_iter : sbl) { - if (!bot_iter) - continue; - - auto bot_message_struct = &bot_iter->GetInspectMessage(); - memset(bot_message_struct, 0, sizeof(InspectMessage_Struct)); - if (set_flag) - memcpy(bot_message_struct, client_message_struct, sizeof(InspectMessage_Struct)); - - if (ab_type != ActionableBots::ABT_All && !database.botdb.SaveInspectMessage(bot_iter->GetBotID(), *bot_message_struct)) { - return; - } - - ++bot_count; - } - - if (ab_type == ActionableBots::ABT_All) { - InspectMessage_Struct bot_message_struct; - memset(&bot_message_struct, 0, sizeof(InspectMessage_Struct)); - if (set_flag) - memcpy(&bot_message_struct, client_message_struct, sizeof(InspectMessage_Struct)); - - if (!database.botdb.SaveAllInspectMessages(c->CharacterID(), bot_message_struct)) { - return; - } - - c->Message(Chat::White, "%s all of your bot inspect messages", set_flag ? "Set" : "Cleared"); - } - else { - c->Message(Chat::White, "%s %i of your spawned bot inspect messages", set_flag ? "Set" : "Cleared", bot_count); - } -} - -void bot_subcommand_bot_list(Client *c, const Seperator *sep) -{ - 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( - Chat::White, - fmt::format( - "Usage: {} (account) ([class] [value]) ([race] [value]) ([name] [partial-full])", - sep->arg[0] - ).c_str() - ); - c->Message(Chat::White, "Note: Filter criteria is orderless and optional."); - return; - } - - bool Account = false; - int seps = 1; - uint32 filter_value[FilterCount]; - int name_criteria_arg = 0; - memset(&filter_value, 0, sizeof(uint32) * FilterCount); - - int filter_mask = 0; - if (!strcasecmp(sep->arg[1], "account")) { - Account = true; - seps = 2; - } - - for (int i = seps; i < (FilterCount * 2); i += 2) { - if (sep->arg[i][0] == '\0') { - break; - } - - if (!strcasecmp(sep->arg[i], "class")) { - filter_mask |= MaskClass; - filter_value[FilterClass] = Strings::ToInt(sep->arg[i + 1]); - continue; - } - - if (!strcasecmp(sep->arg[i], "race")) { - filter_mask |= MaskRace; - filter_value[FilterRace] = Strings::ToInt(sep->arg[i + 1]); - continue; - } - - if (!strcasecmp(sep->arg[i], "name")) { - filter_mask |= MaskName; - name_criteria_arg = (i + 1); - continue; - } - - c->Message(Chat::White, "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::list bots_list; - if (!database.botdb.LoadBotsList(c->CharacterID(), bots_list, Account)) { - return; - } - - if (bots_list.empty()) { - c->Message(Chat::White, "You have no bots."); - return; - } - - auto bot_count = 0; - auto bots_owned = 0; - auto bot_number = 1; - for (auto bots_iter : bots_list) { - if (filter_mask) { - if ((filter_mask & MaskClass) && filter_value[FilterClass] != bots_iter.class_) { - continue; - } - - if ((filter_mask & MaskRace) && filter_value[FilterRace] != bots_iter.race) { - 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 = bots_iter.bot_name; - std::transform(name_check.begin(), name_check.end(), name_check.begin(), ::tolower); - if (name_check.find(name_criteria) == std::string::npos) { - continue; - } - } - } - - auto* bot = entity_list.GetBotByBotName(bots_iter.bot_name); - - c->Message( - Chat::White, - fmt::format( - "Bot {} | {} is a Level {} {} {} {} owned by {}.", - bot_number, - ( - (c->CharacterID() == bots_iter.owner_id && !bot) ? - Saylink::Silent( - fmt::format("^spawn {}", bots_iter.bot_name), - bots_iter.bot_name - ) : - bots_iter.bot_name - ), - bots_iter.level, - GetGenderName(bots_iter.gender), - GetRaceIDName(bots_iter.race), - GetClassIDName(bots_iter.class_), - bots_iter.owner_name - ).c_str() - ); - - if (c->CharacterID() == bots_iter.owner_id) { - bots_owned++; - } - - bot_count++; - bot_number++; - } - - if (!bot_count) { - c->Message(Chat::White, "You have no bots meeting this criteria."); - return; - } else { - c->Message( - Chat::White, - fmt::format( - "{} Of {} bot{} shown, {} {} owned by you.", - bot_count, - bots_list.size(), - bot_count != 1 ? "s" : "", - bots_owned, - bots_owned != 1 ? "are" : "is" - ).c_str() - ); - - c->Message(Chat::White, "Note: You can spawn any owned bots by clicking their name if they are not already spawned."); - - c->Message(Chat::White, "Your bot creation limits are as follows:"); - - const auto overall_bot_creation_limit = c->GetBotCreationLimit(); - - c->Message( - Chat::White, - fmt::format( - "Overall | {} Bot{}", - overall_bot_creation_limit, - overall_bot_creation_limit != 1 ? "s" : "" - ).c_str() - ); - - for (uint8 class_id = Class::Warrior; class_id <= Class::Berserker; class_id++) { - auto class_creation_limit = c->GetBotCreationLimit(class_id); - - if (class_creation_limit != overall_bot_creation_limit) { - c->Message( - Chat::White, - fmt::format( - "{} | {} Bot{}", - GetClassIDName(class_id), - class_creation_limit, - class_creation_limit != 1 ? "s" : "" - ).c_str() - ); - } - } - } -} - -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(Chat::White, "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_surname(Client *c, const Seperator *sep) -{ - if (sep->arg[1][0] == '\0' || sep->IsNumber(1)) { - c->Message(Chat::White, "You must specify a [surname] to use this command (use _ to define spaces or -remove to clear.)"); - return; - } - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must a bot that you own to use this command"); - return; - } - if (strlen(sep->arg[1]) > 31) { - c->Message(Chat::White, "Surname must be 31 characters or less."); - return; - } - std::string bot_surname = sep->arg[1]; - bot_surname = (bot_surname == "-remove") ? "" : bot_surname; - std::replace(bot_surname.begin(), bot_surname.end(), '_', ' '); - - my_bot->SetSurname(bot_surname); - if (database.botdb.SaveBot(my_bot)) { - c->Message(Chat::White, "Bot Surname Saved."); - } -} - -void bot_subcommand_bot_title(Client *c, const Seperator *sep) -{ - if (sep->arg[1][0] == '\0' || sep->IsNumber(1)) { - c->Message(Chat::White, "You must specify a [title] to use this command. (use _ to define spaces or -remove to clear.)"); - return; - } - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must a bot that you own to use this command"); - return; - } - if (strlen(sep->arg[1]) > 31) { - c->Message(Chat::White, "Title must be 31 characters or less."); - return; - } - std::string bot_title = sep->arg[1]; - bot_title = (bot_title == "-remove") ? "" : bot_title; - std::replace(bot_title.begin(), bot_title.end(), '_', ' '); - - my_bot->SetTitle(bot_title); - if (database.botdb.SaveBot(my_bot)) { - c->Message(Chat::White, "Bot Title Saved."); - } -} - -void bot_subcommand_bot_suffix(Client *c, const Seperator *sep) -{ - if (sep->arg[1][0] == '\0' || sep->IsNumber(1)) { - c->Message(Chat::White, "You must specify a [suffix] to use this command. (use _ to define spaces or -remove to clear.)"); - return; - } - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must a bot that you own to use this command"); - return; - } - if (strlen(sep->arg[1]) > 31) { - c->Message(Chat::White, "Suffix must be 31 characters or less."); - return; - } - std::string bot_suffix = sep->arg[1]; - bot_suffix = (bot_suffix == "-remove") ? "" : bot_suffix; - std::replace(bot_suffix.begin(), bot_suffix.end(), '_', ' '); - - my_bot->SetSuffix(bot_suffix); - if (database.botdb.SaveBot(my_bot)) { - c->Message(Chat::White, "Bot Suffix Saved."); - } -} - -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(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | 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()) { - auto t = c->GetTarget(); - if (t && t->IsClient()) { - if (t->CastToClient() == c) { - ab_type_arg = "ownergroup"; - } else { - ab_type_arg = "targetgroup"; - } - } else { - ab_type_arg = "spawned"; - } - } - - std::list sbl; - if (ActionableBots::PopulateSBL(c, ab_type_arg, 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", GetClassIDName(bot_iter->GetClass()), 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(Chat::White, "%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( - Chat::White, - fmt::format( - "Usage: {} [bot_name]", - sep->arg[0] - ).c_str() - ); - return; - } - - auto bot_character_level = c->GetBotRequiredLevel(); - if ( - bot_character_level >= 0 && - c->GetLevel() < bot_character_level && - !c->GetGM() - ) { - c->Message( - Chat::White, - fmt::format( - "You must be level {} to spawn bots.", - bot_character_level - ).c_str() - ); - return; - } - - if (!Bot::CheckSpawnConditions(c)) { - return; - } - - auto bot_spawn_limit = c->GetBotSpawnLimit(); - auto spawned_bot_count = Bot::SpawnedBotCount(c->CharacterID()); - - if ( - bot_spawn_limit >= 0 && - spawned_bot_count >= bot_spawn_limit && - !c->GetGM() - ) { - std::string message; - if (bot_spawn_limit) { - message = fmt::format( - "You cannot have more than {} spawned bot{}.", - bot_spawn_limit, - bot_spawn_limit != 1 ? "s" : "" - ); - } else { - message = "You are not currently allowed to spawn any bots."; - } - - c->Message(Chat::White, message.c_str()); - return; - } - - if (sep->arg[1][0] == '\0' || sep->IsNumber(1)) { - c->Message(Chat::White, "You must specify a name to use this command."); - return; - } - - std::string bot_name = sep->arg[1]; - - uint32 bot_id = 0; - uint8 bot_class = Class::None; - if (!database.botdb.LoadBotID(bot_name, bot_id, bot_class)) { - c->Message( - Chat::White, - fmt::format( - "Failed to load bot ID for '{}'.", - bot_name - ).c_str() - ); - return; - } - - auto bot_spawn_limit_class = c->GetBotSpawnLimit(bot_class); - auto spawned_bot_count_class = Bot::SpawnedBotCount(c->CharacterID(), bot_class); - - if ( - bot_spawn_limit_class >= 0 && - spawned_bot_count_class >= bot_spawn_limit_class && - !c->GetGM() - ) { - std::string message; - - if (bot_spawn_limit_class) { - message = fmt::format( - "You cannot have more than {} spawned {} bot{}.", - bot_spawn_limit_class, - GetClassIDName(bot_class), - bot_spawn_limit_class != 1 ? "s" : "" - ); - } else { - message = fmt::format( - "You are not currently allowed to spawn any {} bots.", - GetClassIDName(bot_class) - ); - } - - c->Message(Chat::White, message.c_str()); - return; - } - - auto bot_character_level_class = c->GetBotRequiredLevel(bot_class); - if ( - bot_character_level_class >= 0 && - c->GetLevel() < bot_character_level_class && - !c->GetGM() - ) { - c->Message( - Chat::White, - fmt::format( - "You must be level {} to spawn {} bots.", - bot_character_level_class, - GetClassIDName(bot_class) - ).c_str() - ); - return; - } - - if (!bot_id) { - c->Message( - Chat::White, - fmt::format( - "You don't own a bot named '{}'.", - bot_name - ).c_str() - ); - return; - } - - if (entity_list.GetMobByBotID(bot_id)) { - c->Message( - Chat::White, - fmt::format( - "'{}' is already spawned.", - bot_name - ).c_str() - ); - return; - } - - auto my_bot = Bot::LoadBot(bot_id); - if (!my_bot) { - c->Message( - Chat::White, - fmt::format( - "Invalid bot '{}' (ID {})", - bot_name, - bot_id - ).c_str() - ); - return; - } - - if (!my_bot->Spawn(c)) { - c->Message( - Chat::White, - fmt::format( - "Failed to spawn '{}' (ID {})", - bot_name, - bot_id - ).c_str() - ); - safe_delete(my_bot); - return; - } - - static std::string bot_spawn_message[17] = { - "I am ready to fight!", // DEFAULT - "A solid weapon is my ally!", // Class::Warrior - "The pious shall never die!", // Class::Cleric - "I am the symbol of Light!", // Class::Paladin - "There are enemies near!", // Class::Ranger - "Out of the shadows, I step!", // Class::ShadowKnight - "Nature's fury shall be wrought!", // Class::Druid - "Your punishment will be my fist!", // Class::Monk - "Music is the overture of battle! ", // BARD - "Daggers into the backs of my enemies!", // Class::Rogue - "More bones to grind!", // Class::Shaman - "Death is only the beginning!", // Class::Necromancer - "I am the harbinger of demise!", // Class::Wizard - "The elements are at my command!", // Class::Magician - "No being can resist my charm!", // Class::Enchanter - "Battles are won by hand and paw!", // Class::Beastlord - "My bloodthirst shall not be quenched!" // Class::Berserker - }; - - uint8 message_index = 0; - if (c->GetBotOption(Client::booSpawnMessageClassSpecific)) { - message_index = VALIDATECLASSID(my_bot->GetClass()); - } - - if (c->GetBotOption(Client::booSpawnMessageSay)) { - Bot::BotGroupSay(my_bot, bot_spawn_message[message_index].c_str()); - } else if (c->GetBotOption(Client::booSpawnMessageTell)) { - my_bot->OwnerMessage(bot_spawn_message[message_index]); - } -} - -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(Chat::White, "usage: %s [current | value: 1-9] ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); - c->Message(Chat::White, "value: %u(%s), %u(%s), %u(%s), %u(%s), %u(%s), %u(%s), %u(%s)", - EQ::constants::stancePassive, EQ::constants::GetStanceName(EQ::constants::stancePassive), - EQ::constants::stanceBalanced, EQ::constants::GetStanceName(EQ::constants::stanceBalanced), - EQ::constants::stanceEfficient, EQ::constants::GetStanceName(EQ::constants::stanceEfficient), - EQ::constants::stanceReactive, EQ::constants::GetStanceName(EQ::constants::stanceReactive), - EQ::constants::stanceAggressive, EQ::constants::GetStanceName(EQ::constants::stanceAggressive), - EQ::constants::stanceAssist, EQ::constants::GetStanceName(EQ::constants::stanceAssist), - EQ::constants::stanceBurn, EQ::constants::GetStanceName(EQ::constants::stanceBurn), - EQ::constants::stanceEfficient2, EQ::constants::GetStanceName(EQ::constants::stanceEfficient2), - EQ::constants::stanceBurnAE, EQ::constants::GetStanceName(EQ::constants::stanceBurnAE) - ); - return; - } - int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); - - bool current_flag = false; - auto bst = EQ::constants::stanceUnknown; - - if (!strcasecmp(sep->arg[1], "current")) - current_flag = true; - else if (sep->IsNumber(1)) { - bst = (EQ::constants::StanceType)Strings::ToInt(sep->arg[1]); - if (bst < EQ::constants::stanceUnknown || bst > EQ::constants::stanceBurnAE) - bst = EQ::constants::stanceUnknown; - } - - if (!current_flag && bst == EQ::constants::stanceUnknown) { - c->Message(Chat::White, "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->Save(); - } - - Bot::BotGroupSay( - bot_iter, - fmt::format( - "My current stance is {} ({}).", - EQ::constants::GetStanceName(bot_iter->GetBotStance()), - bot_iter->GetBotStance() - ).c_str() - ); - } -} - -void bot_subcommand_bot_stop_melee_level(Client *c, const Seperator *sep) -{ - if (helper_command_alias_fail(c, "bot_subcommand_bot_stop_melee_level", sep->arg[0], "botstopmeleelevel")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [current | reset | sync | value: 0-255]", sep->arg[0]); - c->Message(Chat::White, "note: Only caster or hybrid class bots may be modified"); - c->Message(Chat::White, "note: Use [reset] to set stop melee level to server rule"); - c->Message(Chat::White, "note: Use [sync] to set stop melee level to current bot level"); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must a bot that you own to use this command"); - return; - } - if (!IsCasterClass(my_bot->GetClass()) && !IsHybridClass(my_bot->GetClass())) { - c->Message(Chat::White, "You must a caster or hybrid class bot to use this command"); - return; - } - - uint8 sml = RuleI(Bots, CasterStopMeleeLevel); - - if (sep->IsNumber(1)) { - sml = Strings::ToInt(sep->arg[1]); - } - else if (!strcasecmp(sep->arg[1], "sync")) { - sml = my_bot->GetLevel(); - } - else if (!strcasecmp(sep->arg[1], "current")) { - c->Message(Chat::White, "My current melee stop level is %u", my_bot->GetStopMeleeLevel()); - return; - } - else if (strcasecmp(sep->arg[1], "reset")) { - c->Message(Chat::White, "A [current] or [reset] argument, or numeric [value] is required to use this command"); - return; - } - // [reset] falls through with initialization value - - my_bot->SetStopMeleeLevel(sml); - database.botdb.SaveStopMeleeLevel(my_bot->GetBotID(), sml); - - c->Message(Chat::White, "Successfully set stop melee level for %s to %u", my_bot->GetCleanName(), sml); -} - -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(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); - return; - } - const int ab_mask = ActionableBots::ABM_Type1; - - std::string class_race_arg = sep->arg[1]; - bool class_race_check = false; - if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { - class_race_check = true; - } - - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, !class_race_check ? sep->arg[2] : nullptr, class_race_check ? atoi(sep->arg[2]) : 0) == ActionableBots::ABT_None) { - return; - } - - sbl.remove(nullptr); - - for (auto bot_iter : sbl) { - if (!bot_iter) { - continue; - } - - bot_iter->WipeHateList(); - bot_iter->SetTarget(nullptr); - bot_iter->Teleport(c->GetPosition()); - bot_iter->DoAnim(0); - - if (!bot_iter->HasPet()) { - continue; - } - - bot_iter->GetPet()->WipeHateList(); - bot_iter->GetPet()->SetTarget(nullptr); - bot_iter->GetPet()->Teleport(c->GetPosition()); - } - - if (sbl.size() == 1) { - c->Message( - Chat::White, - fmt::format( - "Summoned {} to you.", - sbl.front() ? sbl.front()->GetCleanName() : "no one" - ).c_str() - ); - } - else { - c->Message( - Chat::White, - fmt::format( - "Summoned {} bots to you.", - sbl.size() - ).c_str() - ); - } -} - -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(Chat::White, "usage: %s [value: 0-n] (Drakkin bots only)", sep->arg[0]); - c->Message(Chat::White, "note: Actual limit is filter-based"); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must a bot that you own to use this command"); - return; - } - - if (!sep->IsNumber(1)) { - c->Message(Chat::White, "A numeric [value] is required to use this command"); - return; - } - - uint32 uvalue = Strings::ToInt(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(Chat::White, "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->SetBotArcherySetting(!bot_iter->IsBotArcher(), true); - } - else { - bot_iter->SetBotArcherySetting(archer_state, true); - } - bot_iter->ChangeBotArcherWeapons(bot_iter->IsBotArcher()); - - if (bot_iter->GetClass() == Class::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(Chat::White, "usage: %s ([option: on | off]) ([actionable: target | byname | ownergroup | 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 (!database.botdb.SaveHelmAppearance(bot_iter->GetBotID(), bot_iter->GetShowHelm())) { - 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 = AppearanceType::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) { - if (toggle_helm) { - database.botdb.ToggleAllHelmAppearances(c->CharacterID()); - } - else { - database.botdb.SaveAllHelmAppearances(c->CharacterID(), helm_state); - } - - c->Message(Chat::White, "%s all of your bot show helm flags", toggle_helm ? "Toggled" : (helm_state ? "Set" : "Cleared")); - } - else { - c->Message(Chat::White, "%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 AppearanceType::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(Chat::White, "usage: %s", sep->arg[0]); - return; - } - - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - if (sbl.empty()) { - c->Message(Chat::White, "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(c->GetBotOption(Client::booStatsUpdate)); - bot_iter->SendAppearancePacket(AppearanceType::WhoLevel, bot_iter->GetLevel(), true, true); - ++bot_count; - } - - c->Message(Chat::White, "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(Chat::White, "usage: %s [value: 0-n] (Barbarian bots only)", sep->arg[0]); - c->Message(Chat::White, "note: Actual limit is filter-based"); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must a bot that you own to use this command"); - return; - } - - if (!sep->IsNumber(1)) { - c->Message(Chat::White, "A numeric [value] is required to use this command"); - return; - } - - uint8 uvalue = Strings::ToInt(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_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(Chat::White, "usage: %s [list | destination] ([option: single])", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Depart, Class::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, Class::Druid); - helper_command_depart_list(c, my_druid_bot, nullptr, local_list, single); - return; - } - else if (destination.empty()) { - c->Message(Chat::White, "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 != 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(Chat::White, "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(Chat::White, "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); - return; - } - - if (!current_member->IsHealRotationMember()) { - c->Message(Chat::White, "%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(Chat::White, "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(Chat::White, "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(Chat::White, "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(Chat::White, "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(Chat::White, "Error: New member bot dereferenced to nullptr"); - return; - } - - if (new_member->IsHealRotationMember()) { - c->Message(Chat::White, "%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(Chat::White, "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); - return; - } - - if (!current_member->IsHealRotationMember()) { - c->Message(Chat::White, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); - return; - } - - if (!new_member->JoinHealRotationMemberPool(current_member->MemberOfHealRotation())) { - c->Message(Chat::White, "Failed to add %s as a current member of this Heal Rotation", new_member->GetCleanName()); - return; - } - - c->Message(Chat::White, "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(Chat::White, "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(Chat::White, "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); - return; - } - - if (!current_member->IsHealRotationMember()) { - c->Message(Chat::White, "%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(Chat::White, "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(Chat::White, "%s's entity type is not an allowable heal target", heal_target->GetCleanName()); - return; - } - - if (!heal_target->JoinHealRotationTargetPool(current_member->MemberOfHealRotation())) { - c->Message(Chat::White, "Failed to add heal target with a name of '%s'", heal_target->GetCleanName()); - return; - } - - c->Message(Chat::White, "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(Chat::White, "usage: () %s [armor_type] [value: %3.1f-%3.1f | + | -] ([member_name])", sep->arg[0], CRITICAL_HP_RATIO_BASE, SAFE_HP_RATIO_BASE); - c->Message(Chat::White, "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 = Strings::ToInt(armor_type_arg); - - if (armor_type_value > ARMOR_TYPE_LAST) { - c->Message(Chat::White, "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(Chat::White, "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); - return; - } - - if (!current_member->IsHealRotationMember()) { - c->Message(Chat::White, "%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 = Strings::ToFloat(critical_arg); - 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_ABS) - critical_ratio = SAFE_HP_RATIO_ABS; - if (critical_ratio < CRITICAL_HP_RATIO_ABS) - critical_ratio = CRITICAL_HP_RATIO_ABS; - - if (!(*current_member->MemberOfHealRotation())->SetArmorTypeCriticalHPRatio(armor_type_value, critical_ratio)) { - c->Message(Chat::White, "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(Chat::White, "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(Chat::White, "usage: () %s [armor_type] [value: %3.1f-%3.1f | + | -] ([member_name])", sep->arg[0], CRITICAL_HP_RATIO_BASE, SAFE_HP_RATIO_BASE); - c->Message(Chat::White, "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 = Strings::ToInt(armor_type_arg); - - if (armor_type_value > ARMOR_TYPE_LAST) { - c->Message(Chat::White, "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(Chat::White, "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); - return; - } - - if (!current_member->IsHealRotationMember()) { - c->Message(Chat::White, "%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 = Strings::ToFloat(safe_arg); - 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_ABS) - safe_ratio = SAFE_HP_RATIO_ABS; - if (safe_ratio < CRITICAL_HP_RATIO_ABS) - safe_ratio = CRITICAL_HP_RATIO_ABS; - - if (!(*current_member->MemberOfHealRotation())->SetArmorTypeSafeHPRatio(armor_type_value, safe_ratio)) { - c->Message(Chat::White, "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(Chat::White, "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(Chat::White, "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(Chat::White, "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); - return; - } - - if (!current_member->IsHealRotationMember()) { - c->Message(Chat::White, "%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(Chat::White, "Casting override is 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(Chat::White, "Casting override is 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(Chat::White, "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(Chat::White, "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); - return; - } - - if (!current_member->IsHealRotationMember()) { - c->Message(Chat::White, "%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 = Strings::ToInt(change_interval_arg); - } - else { - hr_change_interval_s = (*current_member->MemberOfHealRotation())->IntervalS(); - c->Message(Chat::White, "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(Chat::White, "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_hot(Client *c, const Seperator *sep) -{ - if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_clear_hot", sep->arg[0], "healrotationclearhot")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "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(Chat::White, "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); - return; - } - - if (!current_member->IsHealRotationMember()) { - c->Message(Chat::White, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); - return; - } - - if (!(*current_member->MemberOfHealRotation())->ClearHOTTarget()) { - c->Message(Chat::White, "Failed to clear %s's Heal Rotation HOT", current_member->GetCleanName()); - } - - c->Message(Chat::White, "Succeeded in clearing %s's Heal Rotation HOT", 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(Chat::White, "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(Chat::White, "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); - return; - } - - if (!current_member->IsHealRotationMember()) { - c->Message(Chat::White, "%s is not a member of a Heal Rotation", current_member->GetCleanName()); - return; - } - - if (!(*current_member->MemberOfHealRotation())->ClearTargetPool()) { - c->Message(Chat::White, "Failed to clear all targets from %s's Heal Rotation", current_member->GetCleanName()); - return; - } - - c->Message(Chat::White, "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(Chat::White, "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(Chat::White, "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(Chat::White, "Error: Creator bot dereferenced to nullptr"); - return; - } - - if (creator_member->IsHealRotationMember()) { - c->Message(Chat::White, "%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 = Strings::ToInt(interval_arg); - } - 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 = Strings::ToInt(interval_arg); - } - - 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(Chat::White, "Failed to add %s as a current member to a new Heal Rotation", creator_member->GetCleanName()); - return; - } - - std::list member_list; - std::list target_list; - bool load_flag = false; - bool member_fail = false; - bool target_fail = false; - - database.botdb.LoadHealRotation(creator_member, member_list, target_list, load_flag, member_fail, target_fail); - - if (!load_flag) { - c->Message(Chat::White, "Successfully added %s as a current member to a new Heal Rotation", creator_member->GetCleanName()); - return; - } - - if (!member_fail) { - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - for (auto member_iter : member_list) { - if (!member_iter || member_iter == creator_member->GetBotID()) - continue; - - bool member_found = false; - for (auto bot_iter : sbl) { - if (bot_iter->GetBotID() != member_iter) - continue; - - if (!bot_iter->JoinHealRotationMemberPool(creator_member->MemberOfHealRotation())) - c->Message(Chat::White, "Failed to add member '%s'", bot_iter->GetCleanName()); - member_found = true; - - break; - } - - if (!member_found) - c->Message(Chat::White, "Could not locate member with bot id '%u'", member_iter); - } - } - - if (!target_fail) { - for (auto target_iter : target_list) { - if (target_iter.empty()) - continue; - - auto target_mob = entity_list.GetMob(target_iter.c_str()); - if (!target_mob) { - c->Message(Chat::White, "Could not locate target '%s'", target_iter.c_str()); - continue; - } - - if (!target_mob->JoinHealRotationTargetPool(creator_member->MemberOfHealRotation())) - c->Message(Chat::White, "Failed to add target '%s'", target_mob->GetCleanName()); - } - } - - c->Message(Chat::White, "Successfully loaded %s's Heal Rotation", creator_member->GetCleanName()); -} - -void bot_subcommand_heal_rotation_delete(Client *c, const Seperator *sep) -{ - if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_delete", sep->arg[0], "healrotationdelete")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s ([option: all]) ([member_name])", sep->arg[0]); - return; - } - - bool all_flag = false; - int name_arg = 1; - if (!strcasecmp(sep->arg[1], "all")) { - all_flag = true; - name_arg = 2; - } - - if (all_flag) { - database.botdb.DeleteAllHealRotations(c->CharacterID()); - return; - } - - std::list sbl; - MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[name_arg]); - if (sbl.empty()) - MyBots::PopulateSBL_ByTargetedBot(c, sbl); - if (sbl.empty()) { - c->Message(Chat::White, "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); - return; - } - - if (!database.botdb.DeleteHealRotation(current_member->GetBotID())) { - return; - } - - c->Message(Chat::White, "Succeeded in deleting %s's heal rotation", current_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(Chat::White, "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(Chat::White, "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); - return; - } - - if (!current_member->IsHealRotationMember()) { - c->Message(Chat::White, "%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(Chat::White, "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(Chat::White, "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(Chat::White, "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(Chat::White, "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); - return; - } - - if (!current_member->IsHealRotationMember()) { - c->Message(Chat::White, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); - return; - } - - c->Message(Chat::White, "Heal Rotation Settings:"); - - c->Message(Chat::White, "Current state: %s", (((*current_member->MemberOfHealRotation())->IsActive()) ? ("active") : ("inactive"))); - c->Message(Chat::White, "Casting interval: %i seconds", (*current_member->MemberOfHealRotation())->IntervalS()); - c->Message(Chat::White, "Fast heals: '%s'", (((*current_member->MemberOfHealRotation())->FastHeals()) ? ("on") : ("off"))); - c->Message(Chat::White, "Adaptive targeting: '%s'", (((*current_member->MemberOfHealRotation())->AdaptiveTargeting()) ? ("on") : ("off"))); - c->Message(Chat::White, "Casting override: '%s'", (((*current_member->MemberOfHealRotation())->CastingOverride()) ? ("on") : ("off"))); - c->Message(Chat::White, "HOT state: %s", (((*current_member->MemberOfHealRotation())->IsHOTActive()) ? ("active") : ("inactive"))); - c->Message(Chat::White, "HOT target: %s", (((*current_member->MemberOfHealRotation())->HOTTarget()) ? ((*current_member->MemberOfHealRotation())->HOTTarget()->GetCleanName()) : ("null"))); - - c->Message(Chat::White, "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(Chat::White, "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(Chat::White, "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(Chat::White, "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(Chat::White, "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(Chat::White, "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(Chat::White, "(%i) %s", (++member_index), member_iter->GetCleanName()); - } - if (!member_index) - c->Message(Chat::White, "(0) None"); - - c->Message(Chat::White, "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(Chat::White, "(%i) %s", (++target_index), target_iter->GetCleanName()); - } - if (!target_index) - c->Message(Chat::White, "(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(Chat::White, "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(Chat::White, "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); - return; - } - - if (!current_member->IsHealRotationMember()) { - c->Message(Chat::White, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); - return; - } - - if (!current_member->LeaveHealRotationMemberPool()) { - c->Message(Chat::White, "Failed to remove %s from their Heal Rotation", current_member->GetCleanName()); - return; - } - - c->Message(Chat::White, "%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(Chat::White, "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(Chat::White, "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); - return; - } - - if (!current_member->IsHealRotationMember()) { - c->Message(Chat::White, "%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(Chat::White, "No target exists by the name '%s'", sep->arg[1]); - return; - } - - if (!current_member->MemberOfHealRotation()->get()->IsTargetInPool(heal_target) || !heal_target->LeaveHealRotationTargetPool()) { - c->Message(Chat::White, "Failed to remove heal target with a name of '%s'", heal_target->GetCleanName()); - return; - } - - c->Message(Chat::White, "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(Chat::White, "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(Chat::White, "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); - return; - } - - if (!current_member->IsHealRotationMember()) { - c->Message(Chat::White, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); - return; - } - - (*current_member->MemberOfHealRotation())->ResetArmorTypeHPLimits(); - - c->Message(Chat::White, "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_save(Client *c, const Seperator *sep) -{ - if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_save", sep->arg[0], "healrotationsave")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "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(Chat::White, "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); - return; - } - - if (!current_member->IsHealRotationMember()) { - c->Message(Chat::White, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); - return; - } - - bool member_fail = false; - bool target_fail = false; - if (!database.botdb.SaveHealRotation(current_member, member_fail, target_fail)) { - return; - } - if (member_fail) - c->Message(Chat::White, "Failed to save heal rotation members"); - if (target_fail) - c->Message(Chat::White, "Failed to save heal rotation targets"); - - c->Message(Chat::White, "Succeeded in saving %s's heal rotation", current_member->GetCleanName()); -} - -void bot_subcommand_heal_rotation_set_hot(Client *c, const Seperator *sep) -{ - if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_set_hot", sep->arg[0], "healrotationsethot")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: () %s [heal_override_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(Chat::White, "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); - return; - } - - if (!current_member->IsHealRotationMember()) { - c->Message(Chat::White, "%s is not a member of a Heal Rotation", current_member->GetCleanName()); - return; - } - - auto hot_target = entity_list.GetMob(sep->arg[1]); - if (!hot_target) { - c->Message(Chat::White, "No target exists by the name '%s'", sep->arg[1]); - return; - } - - if (!(*current_member->MemberOfHealRotation())->IsTargetInPool(hot_target)) { - c->Message(Chat::White, "%s is not a target in %s's Heal Rotation", hot_target->GetCleanName(), current_member->GetCleanName()); - return; - } - - if (!(*current_member->MemberOfHealRotation())->SetHOTTarget(hot_target)) { - c->Message(Chat::White, "Failed to set %s as the HOT in %s's Heal Rotation", hot_target->GetCleanName(), current_member->GetCleanName()); - return; - } - - c->Message(Chat::White, "Succeeded in setting %s as the HOT in %s's Heal Rotation", hot_target->GetCleanName(), 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(Chat::White, "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(Chat::White, "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); - return; - } - - if (!current_member->IsHealRotationMember()) { - c->Message(Chat::White, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); - return; - } - - if ((*current_member->MemberOfHealRotation())->IsActive()) { - c->Message(Chat::White, "%s's Heal Rotation is already active", current_member->GetCleanName()); - return; - } - - if (!current_member->MemberOfHealRotation()->get()->Start()) { - c->Message(Chat::White, "Failed to start %s's Heal Rotation", current_member->GetCleanName()); - return; - } - - c->Message(Chat::White, "%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(Chat::White, "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(Chat::White, "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); - return; - } - - if (!current_member->IsHealRotationMember()) { - c->Message(Chat::White, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); - return; - } - - if (!(*current_member->MemberOfHealRotation())->IsActive()) { - c->Message(Chat::White, "%s's Heal Rotation is already inactive", current_member->GetCleanName()); - return; - } - - if (!current_member->MemberOfHealRotation()->get()->Stop()) { - c->Message(Chat::White, "Failed to stop %s's Heal Rotation", current_member->GetCleanName()); - return; - } - - c->Message(Chat::White, "%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( - Chat::White, - fmt::format( - "Usage: {} ([actionable: target | byname] ([actionable_name]))", - sep->arg[0] - ).c_str() - ); - 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(Chat::White, "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( - Chat::White, - fmt::format( - "Usage: {} ([actionable: target | byname] ([actionable_name]))", - sep->arg[0] - ).c_str() - ); - 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(Chat::White, "ActionableBots returned 'nullptr'"); - return; - } - - const EQ::ItemInstance* inst = nullptr; - const EQ::ItemData* item = nullptr; - bool is_2h_weapon = false; - - EQ::SayLinkEngine linker; - linker.SetLinkType(EQ::saylink::SayLinkItemInst); - - uint32 inventory_count = 0; - for (uint16 slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; ++slot_id) { - if (slot_id == EQ::invslot::slotSecondary && is_2h_weapon) { - continue; - } - - inst = my_bot->CastToBot()->GetBotItem(slot_id); - if (!inst || !inst->GetItem()) { - c->Message( - Chat::White, - fmt::format( - "Slot {} ({}) | Empty", - slot_id, - EQ::invslot::GetInvPossessionsSlotName(slot_id) - ).c_str() - ); - continue; - } - - item = inst->GetItem(); - if (slot_id == EQ::invslot::slotPrimary && item->IsType2HWeapon()) { - is_2h_weapon = true; - } - - linker.SetItemInst(inst); - c->Message( - Chat::White, - fmt::format( - "Slot {} ({}) | {} | {}", - slot_id, - EQ::invslot::GetInvPossessionsSlotName(slot_id), - linker.GenerateLink(), - Saylink::Silent( - fmt::format("^inventoryremove {}", slot_id), - "Remove" - ) - ).c_str() - ); - - ++inventory_count; - } - - uint32 database_count = 0; - database.botdb.QueryInventoryCount(my_bot->GetBotID(), database_count); - - if (inventory_count != database_count) { - c->Message( - Chat::White, - fmt::format( - "Inventory-database item count mismatch, inventory has {} item{} and the database has {} item{}.", - inventory_count, - inventory_count != 1 ? "s" : "", - database_count, - database_count != 1 ? "s" : "" - ).c_str() - ); - } -} - -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( - Chat::White, - fmt::format( - "Usage: {} [Slot ID: 0-22] ([actionable: target | byname] ([actionable_name]))", - sep->arg[0] - ).c_str() - ); - return; - } - - int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); - - if (c->GetTradeskillObject() || (c->trade->state == Trading)) { - c->MessageString(Chat::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(Chat::White, "ActionableBots returned 'nullptr'"); - return; - } - - if (!sep->IsNumber(1)) { - c->Message(Chat::White, "Slot ID must be a number."); - return; - } - - auto slot_id = static_cast(Strings::ToUnsignedInt(sep->arg[1])); - if (slot_id > EQ::invslot::EQUIPMENT_END || slot_id < EQ::invslot::EQUIPMENT_BEGIN) { - c->Message(Chat::White, "Valid slots are 0 to 22."); - return; - } - - auto* inst = my_bot->GetBotItem(slot_id); - if (!inst) { - std::string slot_message = "is"; - switch (slot_id) { - case EQ::invslot::slotShoulders: - case EQ::invslot::slotArms: - case EQ::invslot::slotHands: - case EQ::invslot::slotLegs: - case EQ::invslot::slotFeet: - slot_message = "are"; - break; - default: - break; - } - - my_bot->OwnerMessage( - fmt::format( - "My {} (Slot {}) {} already unequipped.", - EQ::invslot::GetInvPossessionsSlotName(slot_id), - slot_id, - slot_message - ) - ); - return; - } - - const auto* itm = inst->GetItem(); - - if (inst && itm && c->CheckLoreConflict(itm)) { - c->MessageString(Chat::White, PICK_LORE); - return; - } - - for (int m = EQ::invaug::SOCKET_BEGIN; m <= EQ::invaug::SOCKET_END; ++m) { - EQ::ItemInstance *augment = inst->GetAugment(m); - if (!augment) { - continue; - } - - if (!c->CheckLoreConflict(augment->GetItem())) { - continue; - } - - c->MessageString(Chat::White, PICK_LORE); - return; - } - - if (itm) { - EQ::SayLinkEngine linker; - linker.SetLinkType(EQ::saylink::SayLinkItemInst); - linker.SetItemInst(inst); - - c->PushItemOnCursor(*inst, true); - if ( - slot_id == EQ::invslot::slotRange || - slot_id == EQ::invslot::slotAmmo - ) { - my_bot->SetBotArcherySetting(false, true); - } - - my_bot->RemoveBotItemBySlot(slot_id); - my_bot->BotRemoveEquipItem(slot_id); - my_bot->CalcBotStats(c->GetBotOption(Client::booStatsUpdate)); - - my_bot->OwnerMessage( - fmt::format( - "I have unequipped {} from my {} (Slot {}).", - linker.GenerateLink(), - EQ::invslot::GetInvPossessionsSlotName(slot_id), - slot_id - ) - ); - - if (parse->BotHasQuestSub(EVENT_UNEQUIP_ITEM_BOT)) { - const auto& export_string = fmt::format( - "{} {}", - inst->IsStackable() ? inst->GetCharges() : 1, - slot_id - ); - - std::vector args = { inst }; - - parse->EventBot(EVENT_UNEQUIP_ITEM_BOT, my_bot, nullptr, export_string, inst->GetID(), &args); - } - } -} - -void bot_subcommand_inventory_window(Client *c, const Seperator *sep) -{ - if (helper_command_alias_fail(c, "bot_subcommand_inventory_window", sep->arg[0], "inventorywindow")) { - return; - } - - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message( - Chat::White, - fmt::format( - "Usage: {} [actionable: target]", - sep->arg[0] - ).c_str() - ); - return; - } - - int ab_mask = ActionableBots::ABM_Target; - - 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(Chat::White, "ActionableBots returned 'nullptr'"); - return; - } - - std::string window_title = fmt::format( - "{}'s Inventory", - my_bot->GetCleanName() - ); - - std::string window_text = ""; - for (uint16 slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; ++slot_id) { - const EQ::ItemData* item = nullptr; - const EQ::ItemInstance* inst = my_bot->CastToBot()->GetBotItem(slot_id); - if (inst) { - item = inst->GetItem(); - } - - window_text.append( - fmt::format( - "", - EQ::invslot::GetInvPossessionsSlotName(slot_id), - item ? "" : "", - item ? item->Name : "Empty" - ) - ); - } - window_text.append("
{}{}{}
"); - - c->SendPopupToClient( - window_title.c_str(), - window_text.c_str() - ); -} - -void bot_subcommand_pet_get_lost(Client *c, const Seperator *sep) -{ - if (helper_command_alias_fail(c, "bot_subcommand_pet_get_lost", sep->arg[0], "petgetlost")) - return; - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); - return; - } - 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; - - int summoned_pet = 0; - for (auto bot_iter : sbl) { - if (!bot_iter->GetPet() || bot_iter->GetPet()->IsCharmed()) - continue; - - bot_iter->GetPet()->SayString(PET_GETLOST_STRING); - bot_iter->GetPet()->Depop(false); - bot_iter->SetPetID(0); - database.botdb.DeletePetItems(bot_iter->GetBotID()); - database.botdb.DeletePetBuffs(bot_iter->GetBotID()); - database.botdb.DeletePetStats(bot_iter->GetBotID()); - ++summoned_pet; - } - - c->Message(Chat::White, "%i of your bots released their summoned pet%s", summoned_pet, (summoned_pet == 1) ? "" : "s"); -} - -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(Chat::White, "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_bitmasks[Class::Druid] | player_class_bitmasks[Class::Necromancer] | player_class_bitmasks[Class::Enchanter]); - ActionableBots::Filter_ByClasses(c, sbl, class_mask); - if (sbl.empty()) { - c->Message(Chat::White, "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()->SayString(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(Chat::White, "%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(Chat::White, "usage: %s [type: water | fire | air | earth | monster] ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); - c->Message(Chat::White, "requires one of the following bot classes:"); - c->Message(Chat::White, "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(Chat::White, "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_bitmasks[Class::Magician]; - ActionableBots::Filter_ByClasses(c, sbl, class_mask); - if (sbl.empty()) { - c->Message(Chat::White, "You have no spawned Magician bots"); - return; - } - - ActionableBots::Filter_ByMinLevel(c, sbl, level_req); - if (sbl.empty()) { - c->Message(Chat::White, "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(Chat::White, "usage: %s [list | destination] ([option: single])", sep->arg[0]); - helper_send_usage_required_bots(c, BCEnum::SpT_Depart, Class::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, Class::Wizard); - helper_command_depart_list(c, nullptr, my_wizard_bot, local_list, single); - return; - } - else if (destination.empty()) { - c->Message(Chat::White, "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 != 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) { @@ -9956,929 +2126,67 @@ bool helper_spell_list_fail(Client *bot_owner, bcst_list* spell_list, BCEnum::Sp return false; } -void bot_command_spell_list(Client* c, const Seperator *sep) -{ - if (helper_command_alias_fail(c, "bot_command_spell_list", sep->arg[0], "spells")) { - return; - } - - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message( - Chat::White, - fmt::format( - "Usage: {} [Min Level] (Level is optional)", - sep->arg[0] - ).c_str() - ); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must target a bot that you own to use this command."); - return; - } - - uint8 min_level = 0; - - if (sep->IsNumber(1)) { - min_level = static_cast(Strings::ToUnsignedInt(sep->arg[1])); - } - - my_bot->ListBotSpells(min_level); -} - -void bot_command_spell_settings_add(Client *c, const Seperator *sep) -{ - if (helper_command_alias_fail(c, "bot_command_spell_settings_add", sep->arg[0], "spellsettingsadd")) { - return; - } - - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message( - Chat::White, - fmt::format( - "Usage: {} [Spell ID] [Priority] [Min HP] [Max HP]", - sep->arg[0] - ).c_str() - ); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must target a bot that you own to use this command."); - return; - } - - auto arguments = sep->argnum; - if ( - arguments < 4 || - !sep->IsNumber(1) || - !sep->IsNumber(2) || - !sep->IsNumber(3) || - !sep->IsNumber(4) - ) { - c->Message( - Chat::White, - fmt::format( - "Usage: {} [Spell ID] [Priority] [Min HP] [Max HP]", - sep->arg[0] - ).c_str() - ); - return; - } - - auto spell_id = static_cast(Strings::ToUnsignedInt(sep->arg[1])); - - if (!IsValidSpell(spell_id)) { - c->Message( - Chat::White, - fmt::format( - "Spell ID {} is invalid or could not be found.", - spell_id - ).c_str() - ); - return; - } - - if (my_bot->GetBotSpellSetting(spell_id)) { - c->Message( - Chat::White, - fmt::format( - "{} already has a spell setting for {} ({}), trying using {} instead.", - my_bot->GetCleanName(), - spells[spell_id].name, - spell_id, - Saylink::Silent("^spellsettingsupdate") - ).c_str() - ); - return; - } - - auto priority = static_cast(Strings::ToInt(sep->arg[2])); - auto min_hp = static_cast(EQ::Clamp(Strings::ToInt(sep->arg[3]), -1, 99)); - auto max_hp = static_cast(EQ::Clamp(Strings::ToInt(sep->arg[4]), -1, 100)); - - BotSpellSetting bs; - - bs.priority = priority; - bs.min_hp = min_hp; - bs.max_hp = max_hp; - - if (!my_bot->AddBotSpellSetting(spell_id, &bs)) { - c->Message( - Chat::White, - fmt::format( - "Failed to add spell setting for {}.", - my_bot->GetCleanName() - ).c_str() - ); - return; - } - - my_bot->AI_AddBotSpells(my_bot->GetBotSpellID()); - - c->Message( - Chat::White, - fmt::format( - "Successfully added spell setting for {}.", - my_bot->GetCleanName() - ).c_str() - ); - - c->Message( - Chat::White, - fmt::format( - "Spell Setting Added | Spell: {} ({}) ", - spells[spell_id].name, - spell_id - ).c_str() - ); - - c->Message( - Chat::White, - fmt::format( - "Spell Setting Added | Priority: {} Health: {}", - priority, - my_bot->GetHPString(min_hp, max_hp) - ).c_str() - ); -} - -void bot_command_spell_settings_delete(Client *c, const Seperator *sep) -{ - if (helper_command_alias_fail(c, "bot_command_spell_settings_delete", sep->arg[0], "spellsettingsdelete")) { - return; - } - - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message( - Chat::White, - fmt::format( - "Usage: {} [Spell ID]", - sep->arg[0] - ).c_str() - ); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must target a bot that you own to use this command."); - return; - } - - auto arguments = sep->argnum; - if ( - arguments < 1 || - !sep->IsNumber(1) - ) { - c->Message( - Chat::White, - fmt::format( - "Usage: {} [Spell ID]", - sep->arg[0] - ).c_str() - ); - return; - } - - auto spell_id = static_cast(Strings::ToUnsignedInt(sep->arg[1])); - - if (!IsValidSpell(spell_id)) { - c->Message( - Chat::White, - fmt::format( - "Spell ID {} is invalid or could not be found.", - spell_id - ).c_str() - ); - return; - } - - if (!my_bot->DeleteBotSpellSetting(spell_id)) { - c->Message( - Chat::White, - fmt::format( - "Failed to delete spell setting for {}.", - my_bot->GetCleanName() - ).c_str() - ); - return; - } - - my_bot->AI_AddBotSpells(my_bot->GetBotSpellID()); - - c->Message( - Chat::White, - fmt::format( - "Successfully deleted spell setting for {}.", - my_bot->GetCleanName() - ).c_str() - ); - - c->Message( - Chat::White, - fmt::format( - "Spell Setting Deleted | Spell: {} ({})", - spells[spell_id].name, - spell_id - ).c_str() - ); -} - -void bot_command_spell_settings_list(Client *c, const Seperator *sep) -{ - if (helper_command_alias_fail(c, "bot_command_spell_settings_list", sep->arg[0], "spellsettings")) { - return; - } - - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message( - Chat::White, - fmt::format( - "Usage: {}", - sep->arg[0] - ).c_str() - ); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must target a bot that you own to use this command."); - return; - } - - my_bot->ListBotSpellSettings(); -} - -void bot_command_spell_settings_toggle(Client *c, const Seperator *sep) -{ - if (helper_command_alias_fail(c, "bot_command_spell_settings_toggle", sep->arg[0], "spellsettingstoggle")) { - return; - } - - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message( - Chat::White, - fmt::format( - "Usage: {} [Spell ID] [Toggle]", - sep->arg[0] - ).c_str() - ); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must target a bot that you own to use this command."); - return; - } - - auto arguments = sep->argnum; - if ( - arguments < 2 || - !sep->IsNumber(1) - ) { - c->Message( - Chat::White, - fmt::format( - "Usage: {} [Spell ID] [Toggle]", - sep->arg[0] - ).c_str() - ); - return; - } - - auto spell_id = static_cast(Strings::ToUnsignedInt(sep->arg[1])); - if (!IsValidSpell(spell_id)) { - c->Message( - Chat::White, - fmt::format( - "Spell ID {} is invalid or could not be found.", - spell_id - ).c_str() - ); - return; - } - - bool toggle = ( - sep->IsNumber(2) ? - Strings::ToInt(sep->arg[2]) != 0 : - atobool(sep->arg[2]) - ); - - auto obs = my_bot->GetBotSpellSetting(spell_id); - if (!obs) { - return; - } - - BotSpellSetting bs; - - bs.priority = obs->priority; - bs.min_hp = obs->min_hp; - bs.max_hp = obs->max_hp; - bs.is_enabled = toggle; - - if (!my_bot->UpdateBotSpellSetting(spell_id, &bs)) { - c->Message( - Chat::White, - fmt::format( - "Failed to {}able spell for {}.", - toggle ? "en" : "dis", - my_bot->GetCleanName() - ).c_str() - ); - return; - } - - my_bot->AI_AddBotSpells(my_bot->GetBotSpellID()); - - c->Message( - Chat::White, - fmt::format( - "Successfully {}abled spell for {}.", - toggle ? "en" : "dis", - my_bot->GetCleanName() - ).c_str() - ); - - c->Message( - Chat::White, - fmt::format( - "Spell {}abled | Spell: {} ({})", - toggle ? "En" : "Dis", - spells[spell_id].name, - spell_id - ).c_str() - ); -} - -void bot_command_spell_settings_update(Client *c, const Seperator *sep) -{ - if (helper_command_alias_fail(c, "bot_command_spell_settings_update", sep->arg[0], "spellsettingsupdate")) { - return; - } - - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message( - Chat::White, - fmt::format( - "Usage: {} [Spell ID] [Priority] [Min HP] [Max HP]", - sep->arg[0] - ).c_str() - ); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must target a bot that you own to use this command."); - return; - } - - auto arguments = sep->argnum; - if ( - arguments < 4 || - !sep->IsNumber(1) || - !sep->IsNumber(2) || - !sep->IsNumber(3) || - !sep->IsNumber(4) - ) { - c->Message( - Chat::White, - fmt::format( - "Usage: {} [Spell ID] [Priority] [Min HP] [Max HP]", - sep->arg[0] - ).c_str() - ); - return; - } - - auto spell_id = static_cast(Strings::ToUnsignedInt(sep->arg[1])); - - if (!IsValidSpell(spell_id)) { - c->Message( - Chat::White, - fmt::format( - "Spell ID {} is invalid or could not be found.", - spell_id - ).c_str() - ); - return; - } - - auto priority = static_cast(Strings::ToInt(sep->arg[2])); - auto min_hp = static_cast(EQ::Clamp(Strings::ToInt(sep->arg[3]), -1, 99)); - auto max_hp = static_cast(EQ::Clamp(Strings::ToInt(sep->arg[4]), -1, 100)); - - BotSpellSetting bs; - - bs.priority = priority; - bs.min_hp = min_hp; - bs.max_hp = max_hp; - - if (!my_bot->UpdateBotSpellSetting(spell_id, &bs)) { - c->Message( - Chat::White, - fmt::format( - "Failed to update spell setting for {}.", - my_bot->GetCleanName() - ).c_str() - ); - return; - } - - my_bot->AI_AddBotSpells(my_bot->GetBotSpellID()); - - c->Message( - Chat::White, - fmt::format( - "Successfully updated spell setting for {}.", - my_bot->GetCleanName() - ).c_str() - ); - - c->Message( - Chat::White, - fmt::format( - "Spell Setting Updated | Spell: {} ({})", - spells[spell_id].name, - spell_id - ).c_str() - ); - - c->Message( - Chat::White, - fmt::format( - "Spell Setting Updated | Priority: {} Health: {}", - priority, - my_bot->GetHPString(min_hp, max_hp) - ).c_str() - ); -} - -void bot_spell_info_dialogue_window(Client* c, const Seperator *sep) -{ - if (helper_command_alias_fail(c, "bot_spell_info_dialogue_window", sep->arg[0], "spellinfo")) { - return; - } - - auto arguments = sep->argnum; - if ( - arguments < 1 || - !sep->IsNumber(1) - ) { - c->Message( - Chat::White, - fmt::format( - "Usage: {} [Spell ID]", - sep->arg[0] - ).c_str() - ); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must target a bot that you own to use this command."); - return; - } - - auto spell_id = static_cast(Strings::ToUnsignedInt(sep->arg[1])); - auto min_level = spells[spell_id].classes; - auto class_level = min_level[my_bot->GetBotClass() - 1]; - - if (class_level > my_bot->GetLevel()) { - c->Message(Chat::White, "This is not a usable spell by your bot."); - return; - } - - auto results = database.QueryDatabase( - fmt::format( - "SELECT value FROM db_str WHERE id = {} and type = 6 LIMIT 1", - spells[spell_id].effect_description_id - ) - ); - - if (!results.Success() || !results.RowCount()) { - c->Message(Chat::White, "No Spell Information Available for this."); - return; - } - - auto row = results.begin(); - std::string spell_desc = row[0]; - - auto m = DialogueWindow::TableRow( - DialogueWindow::TableCell("Spell Effect: ") + - DialogueWindow::TableCell(spell_desc) - ); - - m += DialogueWindow::TableRow( - DialogueWindow::TableCell("Spell Level: ") + - DialogueWindow::TableCell(fmt::format("{}", class_level)) - ); - - c->SendPopupToClient( - fmt::format( - "Spell: {}", spells[spell_id].name - ).c_str(), - DialogueWindow::Table(m).c_str() - ); -} - -void bot_command_enforce_spell_list(Client* c, const Seperator *sep) -{ - if (helper_command_alias_fail(c, "bot_command_enforce_spell_list", sep->arg[0], "enforcespellsettings")) { - return; - } - - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message( - Chat::White, - fmt::format( - "Usage: {} [True|False] (Blank to toggle]", - sep->arg[0] - ).c_str() - ); - return; - } - - auto my_bot = ActionableBots::AsTarget_ByBot(c); - if (!my_bot) { - c->Message(Chat::White, "You must target a bot that you own to use this command."); - return; - } - - bool enforce_state = (sep->argnum > 0) ? Strings::ToBool(sep->arg[1]) : !my_bot->GetBotEnforceSpellSetting(); - my_bot->SetBotEnforceSpellSetting(enforce_state, true); - - c->Message( - Chat::White, - fmt::format( - "{}'s Spell Settings List entries are now {}.", - my_bot->GetCleanName(), - my_bot->GetBotEnforceSpellSetting() ? "enforced" : "optional" - ).c_str() - ); -} - -void bot_command_caster_range(Client* c, const Seperator* sep) -{ - if (helper_command_alias_fail(c, "bot_command_caster_range", sep->arg[0], "casterrange")) { - return; - } - - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s [current | value: 0 - 300] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); - c->Message(Chat::White, "note: Can only be used for Casters or Hybrids."); - c->Message(Chat::White, "note: Use [current] to check the current setting."); - c->Message(Chat::White, "note: Set the value to the minimum distance you want your bot to try to remain from its target."); - c->Message(Chat::White, "note: If they are too far for a spell, it will be skipped."); - c->Message(Chat::White, "note: This is set to (90) units by default."); - return; - } - const int ab_mask = ActionableBots::ABM_Type1; - - std::string arg1 = sep->arg[1]; - int ab_arg = 1; - bool current_check = false; - uint32 crange = 0; - - if (sep->IsNumber(1)) { - ab_arg = 2; - crange = atoi(sep->arg[1]); - if (crange < 0 || crange > 300) { - c->Message(Chat::White, "You must enter a value within the range of 0 - 300."); - return; - } - } - else if (!arg1.compare("current")) { - ab_arg = 2; - current_check = true; - } - else { - c->Message(Chat::White, "Incorrect argument, use %s help for a list of options.", sep->arg[0]); - return; - } - - std::string class_race_arg = sep->arg[ab_arg]; - bool class_race_check = false; - - if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { - class_race_check = true; - } - - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { - return; - } - - sbl.remove(nullptr); - - Bot* first_found = nullptr; - int success_count = 0; - - for (auto my_bot : sbl) { - if (!IsCasterClass(my_bot->GetClass()) && !IsHybridClass(my_bot->GetClass())) { - continue; - } - - if (!first_found) { - first_found = my_bot; - } - - if (current_check) { - c->Message( - Chat::White, - fmt::format( - "{} says, 'My current Caster Range is {}.'", - my_bot->GetCleanName(), - my_bot->GetBotCasterRange() - ).c_str() - ); - } - else { - my_bot->SetBotCasterRange(crange); - ++success_count; - - database.botdb.SaveBotCasterRange(my_bot->GetBotID(), crange); - } - } - if (!current_check) { - if (success_count == 1 && first_found) { - c->Message( - Chat::White, - fmt::format( - "{} says, 'My Caster Range was set to {}.'", - first_found->GetCleanName(), - crange - ).c_str() - ); - } - else { - c->Message( - Chat::White, - fmt::format( - "{} of your bots set their Caster Range to {}.", - success_count, - crange - ).c_str() - ); - } - } -} - -void bot_command_click_item(Client* c, const Seperator* sep) -{ - if (!RuleB(Bots, BotsCanClickItems)) { - c->Message(Chat::White, "The ability for bots to click equipped items is currently disabled."); - return; - } - - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); - c->Message(Chat::White, "This will cause the selected bots to click the item in the given slot ID."); - c->Message(Chat::White, "Use ^invlist to see their items along with slot IDs."); - return; - } - - if (!sep->IsNumber(1)) { - c->Message(Chat::Yellow, "You must specify a slot ID. Use %s help for more information.", sep->arg[0]); - return; - } - - const int ab_mask = ActionableBots::ABM_Type1; - - int ab_arg = 1; - uint32 slot_id = 0; - - if (sep->IsNumber(1)) { - ab_arg = 2; - slot_id = atoi(sep->arg[1]); - if (slot_id < EQ::invslot::EQUIPMENT_BEGIN || slot_id > EQ::invslot::EQUIPMENT_END) { - c->Message(Chat::Yellow, "You must specify a valid inventory slot from 0 to 22. Use %s help for more information", sep->arg[0]); - return; - } - } - - std::string class_race_arg = sep->arg[ab_arg]; - bool class_race_check = false; - if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { - class_race_check = true; - } - - std::list sbl; - if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { - return; - } - sbl.remove(nullptr); - - for (auto my_bot : sbl) { - if (RuleI(Bots, BotsClickItemsMinLvl) > my_bot->GetLevel()) { - c->Message(Chat::White, "%s must be level %i to use clickable items.", my_bot->GetCleanName(), RuleI(Bots, BotsClickItemsMinLvl)); - continue; - } - - my_bot->TryItemClick(slot_id); - } -} - -void bot_command_pickpocket(Client *c, const Seperator *sep) -{ - if (helper_command_disabled(c, RuleB(Bots, AllowPickpocketCommand), "pickpocket")) { - return; - } - - if (helper_command_alias_fail(c, "bot_command_pickpocket", sep->arg[0], "pickpocket")) { - return; - } - - if (helper_is_help_or_usage(sep->arg[1])) { - c->Message(Chat::White, "usage: ", sep->arg[0]); - return; - } - - std::list sbl; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - - // Check for capable rogue - ActionableBots::Filter_ByClasses(c, sbl, player_class_bitmasks[Class::Rogue]); - Bot *my_bot = ActionableBots::AsSpawned_ByMinLevelAndClass(c, sbl, 7, Class::Rogue); - if (!my_bot) { - c->Message(Chat::White, "No bots are capable of performing this action"); - return; - } - - // Make sure a mob is targetted and a valid NPC - Mob *target_mob = ActionableTarget::AsSingle_ByAttackable(c); - if (!target_mob || !target_mob->IsNPC()) { - c->Message(Chat::White, "You must an enemy to use this command"); - return; - } - - NPC *target_npc = ActionableTarget::AsSingle_ByAttackable(c)->CastToNPC(); - - // Check if mob is close enough - glm::vec4 mob_distance = (c->GetPosition() - target_mob->GetPosition()); - float mob_xy_distance = ((mob_distance.x * mob_distance.x) + (mob_distance.y * mob_distance.y)); - float mob_z_distance = (mob_distance.z * mob_distance.z); - float z_offset_diff = target_mob->GetZOffset() - c->GetZOffset(); - - if (mob_z_distance >= (35-z_offset_diff) || mob_xy_distance > 250) { - c->Message(Chat::White, "You must be closer to an enemy to use this command"); - return; - } - - // Adapted from pickpock skill in npc.cpp - // Make sure we are allowed to target them - uint8 over_level = target_mob->GetLevel(); - if (over_level > (my_bot->GetLevel() + THIEF_PICKPOCKET_OVER)) { - c->Message(Chat::Red, "You are too inexperienced to pick pocket this target"); - return; - } - - // Random fail roll - if (zone->random.Roll(5)) { - if (zone->CanDoCombat()) { - target_mob->AddToHateList(c, 50); - } - target_mob->Say("Stop thief!"); - c->Message(Chat::Red, "You are noticed trying to steal!"); - return; - } - - // Setup variables for calcs - bool steal_skill = my_bot->GetSkill(EQ::skills::SkillPickPockets); - bool steal_chance = steal_skill * 100 / (5 * over_level + 5); - - // Determine whether to steal money or an item. - uint32 money[6] = { - 0, - ((steal_skill >= 125) ? (target_npc->GetPlatinum()) : (0)), - ((steal_skill >= 60) ? (target_npc->GetGold()) : (0)), - target_npc->GetSilver(), - target_npc->GetCopper(), - 0 - }; - - bool has_coin = ((money[PickPocketPlatinum] | money[PickPocketGold] | money[PickPocketSilver] | money[PickPocketCopper]) != 0); - bool steal_item = (steal_skill >= steal_chance && (zone->random.Roll(50) || !has_coin)); - - // Steal item - while (steal_item) { - std::vector> loot_selection; // - for (auto item_iter: target_npc->itemlist) { - if (!item_iter || !item_iter->item_id) { - continue; - } - auto item_test = database.GetItem(item_iter->item_id); - if (item_test->Magic || !item_test->NoDrop || item_test->IsClassBag() || c->CheckLoreConflict(item_test) || - item_iter->equip_slot != EQ::invslot::SLOT_INVALID) { - continue; - } - loot_selection.emplace_back( - std::make_pair( - item_test, - ((item_test->Stackable) ? (1) : (item_iter->charges)) - ) - ); - } - if (loot_selection.empty()) { - steal_item = false; - break; - } - - int random = zone->random.Int(0, (loot_selection.size() - 1)); - - int16 slot_id = c->GetInv().FindFreeSlot( - false, - true, - (loot_selection[random].first->Size), - (loot_selection[random].first->ItemType == EQ::item::ItemTypeArrow) - ); - if (slot_id == INVALID_INDEX) { - steal_item = false; - break; - } - - auto item_inst = database.CreateItem(loot_selection[random].first, loot_selection[random].second); - if (item_inst == nullptr) { - steal_item = false; - break; - } - - // Successful item pickpocket - if (item_inst->IsStackable() && RuleB(Character, UseStackablePickPocketing)) { - if (!c->TryStacking(item_inst, ItemPacketTrade, false, false)) { - c->PutItemInInventory(slot_id, *item_inst); - c->SendItemPacket(slot_id, item_inst, ItemPacketTrade); - } - } - else { - c->PutItemInInventory(slot_id, *item_inst); - c->SendItemPacket(slot_id, item_inst, ItemPacketTrade); - } - target_npc->RemoveItem(item_inst->GetID()); - c->Message(Chat::White, "You stole an item."); - safe_delete(item_inst); - return; - } - - // no items, try money - while (!steal_item && has_coin) { - uint32 coin_amount = zone->random.Int(1, (steal_skill / 25) + 1); - - int coin_type = PickPocketPlatinum; - while (coin_type <= PickPocketCopper) { - if (money[coin_type]) { - if (coin_amount > money[coin_type]) { - coin_amount = money[coin_type]; - } - break; - } - ++coin_type; - } - if (coin_type > PickPocketCopper) { - break; - } - - memset(money, 0, (sizeof(int) * 6)); - money[coin_type] = coin_amount; - - if (zone->random.Roll(steal_chance)) { // Successful coin pickpocket - switch (coin_type) { - case PickPocketPlatinum: - target_npc->SetPlatinum(target_npc->GetPlatinum() - coin_amount); - break; - case PickPocketGold: - target_npc->SetGold(target_npc->GetGold() - coin_amount); - break; - case PickPocketSilver: - target_npc->SetSilver(target_npc->GetSilver() - coin_amount); - break; - case PickPocketCopper: - target_npc->SetCopper(target_npc->GetCopper() - coin_amount); - break; - default: // has_coin..but, doesn't have coin? - c->Message(Chat::Red, "You failed to pickpocket."); - return; - } - c->Message(Chat::White, "You stole money."); - c->AddMoneyToPP( - money[PickPocketCopper], - money[PickPocketSilver], - money[PickPocketGold], - money[PickPocketPlatinum], - true - ); - return; - } - - c->Message(Chat::Red, "You failed to pickpocket."); - return; - } - c->Message(Chat::White, "This target's pockets are empty"); -} +#include "bot_commands/actionable.cpp" +#include "bot_commands/aggressive.cpp" +#include "bot_commands/appearance.cpp" +#include "bot_commands/apply_poison.cpp" +#include "bot_commands/apply_potion.cpp" +#include "bot_commands/attack.cpp" +#include "bot_commands/bind_affinity.cpp" +#include "bot_commands/bot.cpp" +#include "bot_commands/caster_range.cpp" +#include "bot_commands/charm.cpp" +#include "bot_commands/click_item.cpp" +#include "bot_commands/cure.cpp" +#include "bot_commands/defensive.cpp" +#include "bot_commands/depart.cpp" +#include "bot_commands/escape.cpp" +#include "bot_commands/find_aliases.cpp" +#include "bot_commands/follow.cpp" +#include "bot_commands/follow_distance.cpp" +#include "bot_commands/guard.cpp" +#include "bot_commands/heal_rotation.cpp" +#include "bot_commands/help.cpp" +#include "bot_commands/hold.cpp" +#include "bot_commands/identify.cpp" +#include "bot_commands/inspect_message.cpp" +#include "bot_commands/inventory.cpp" +#include "bot_commands/invisibility.cpp" +#include "bot_commands/item_use.cpp" +#include "bot_commands/levitation.cpp" +#include "bot_commands/list.cpp" +#include "bot_commands/lull.cpp" +#include "bot_commands/mesmerize.cpp" +#include "bot_commands/movement_speed.cpp" +#include "bot_commands/name.cpp" +#include "bot_commands/out_of_combat.cpp" +#include "bot_commands/owner_option.cpp" +#include "bot_commands/pet.cpp" +#include "bot_commands/pick_lock.cpp" +#include "bot_commands/pickpocket.cpp" +#include "bot_commands/precombat.cpp" +#include "bot_commands/pull.cpp" +#include "bot_commands/release.cpp" +#include "bot_commands/report.cpp" +#include "bot_commands/resistance.cpp" +#include "bot_commands/resurrect.cpp" +#include "bot_commands/rune.cpp" +#include "bot_commands/send_home.cpp" +#include "bot_commands/size.cpp" +#include "bot_commands/spell.cpp" +#include "bot_commands/stance.cpp" +#include "bot_commands/stop_melee_level.cpp" +#include "bot_commands/suffix.cpp" +#include "bot_commands/summon.cpp" +#include "bot_commands/summon_corpse.cpp" +#include "bot_commands/surname.cpp" +#include "bot_commands/suspend.cpp" +#include "bot_commands/taunt.cpp" +#include "bot_commands/teleport.cpp" +#include "bot_commands/timer.cpp" +#include "bot_commands/toggle_archer.cpp" +#include "bot_commands/toggle_helm.cpp" +#include "bot_commands/track.cpp" +#include "bot_commands/update.cpp" +#include "bot_commands/view_combos.cpp" +#include "bot_commands/water_breathing.cpp" diff --git a/zone/bot_command.h b/zone/bot_command.h index 28bd74daa..78fdba943 100644 --- a/zone/bot_command.h +++ b/zone/bot_command.h @@ -25,7 +25,7 @@ class Seperator; #include "../common/types.h" #include "bot.h" - +#include "dialogue_window.h" class BCEnum { @@ -140,81 +140,1168 @@ public: 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"; + 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"; + 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"; } } }; +namespace +{ +#define HP_RATIO_DELTA 5.0f + + enum { EffectIDFirst = 1, EffectIDLast = 12 }; + +#define VALIDATECLASSID(x) ((x >= Class::Warrior && x <= Class::Berserker) ? (x) : (0)) +#define CLASSIDTOINDEX(x) ((x >= Class::Warrior && x <= Class::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 +} + +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() && !bot_owner->GetRaid()) { + return; + } + + if (bot_owner->IsRaidGrouped()) { + Raid* raid = bot_owner->GetRaid(); + if (!raid) { + return; + } + + uint32 raid_group = raid->GetGroup(bot_owner); + if (raid_group >= MAX_RAID_GROUPS) { + return; + } + + for (const auto& m : raid->members) { + if (m.member && m.group_number == raid_group) { + if (!MyBots::IsMyBot(bot_owner, m.member->CastToBot())) { + continue; + } + + sbl.push_back(m.member->CastToBot()); + } + } + } + else { + 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_ByMyRaidBots(Client* bot_owner, std::list& sbl, bool clear_list = true) { + if (clear_list) { + sbl.clear(); + } + + if (!bot_owner) { + return; + } + + if (!bot_owner->GetRaid()) { + return; + } + + Raid* raid = bot_owner->GetRaid(); + if (!raid) { + return; + } + + for (const auto& m : raid->members) { + if (m.member) { + if (!MyBots::IsMyBot(bot_owner, m.member->CastToBot())) { + continue; + } + + sbl.push_back(m.member->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->GetRaid()) || (!target_mob->IsClient() && !target_mob->IsBot())) { + return; + } + + if (bot_owner->IsRaidGrouped()) { + Raid* raid = bot_owner->GetRaid(); + if (!raid) { + return; + } + + if (MyBots::IsMyBot(bot_owner, target_mob)) { + uint32 raid_group = raid->GetGroup(target_mob->CastToClient()); + if (raid_group >= MAX_RAID_GROUPS) { + return; + } + + for (const auto& m : raid->members) { + if (m.member && m.group_number == raid_group) { + if (!MyBots::IsMyBot(bot_owner, m.member->CastToBot())) { + continue; + } + + sbl.push_back(m.member->CastToBot()); + } + } + } + } + else { + 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_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 || !MyBots::IsMyBot(bot_owner, named_mob) || (!named_mob->GetGroup() && !named_mob->GetRaid()) || (!named_mob->IsClient() && !named_mob->IsBot())) { + return; + } + + if (bot_owner->IsRaidGrouped()) { + Raid* raid = bot_owner->GetRaid(); + if (!raid) { + return; + } + + uint32 raid_group = raid->GetGroup(named_mob->CastToClient()); + if (raid_group >= MAX_RAID_GROUPS) { + return; + } + + for (const auto& m : raid->members) { + if (m.member && m.group_number == raid_group) { + if (!MyBots::IsMyBot(bot_owner, m.member->CastToBot())) { + continue; + } + + sbl.push_back(m.member->CastToBot()); + } + } + } + else { + 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_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); + } + + static void PopulateSBL_BySpawnedBotsClass(Client * bot_owner, std::list &sbl, uint16 cls, bool clear_list = true) { + if (clear_list) { + sbl.clear(); + } + + if (!bot_owner || !cls) { + return; + } + + auto selectable_bot_list = entity_list.GetBotsByBotOwnerCharacterID(bot_owner->CharacterID()); + for (auto bot_iter : selectable_bot_list) { + if (bot_iter->GetClass() != cls) { + continue; + } + + sbl.push_back(bot_iter); + } + + if (!clear_list) { + UniquifySBL(sbl); + } + } + + static void PopulateSBL_BySpawnedBotsRace(Client* bot_owner, std::list& sbl, uint16 race, bool clear_list = true) { + if (clear_list) { + sbl.clear(); + } + + if (!bot_owner || !race) { + return; + } + + auto selectable_bot_list = entity_list.GetBotsByBotOwnerCharacterID(bot_owner->CharacterID()); + for (auto bot_iter : selectable_bot_list) { + if (bot_iter->GetBaseRace() != race) { + continue; + } + + sbl.push_back(bot_iter); + } + + if (!clear_list) { + UniquifySBL(sbl); + } + } +}; + +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 bool IsAttackable(Client *bot_owner, Mob* target_mob) { + if (!bot_owner || !target_mob || bot_owner == target_mob) + return false; + + return bot_owner->IsAttackAllowed(target_mob); + } + + 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 (!IsAttackable(bot_owner, 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 (IsAttackable(bot_owner, bot_owner->GetTarget()) || target_type == BCEnum::TT_None) + return nullptr; + + auto target_mob = bot_owner->GetTarget(); + 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 (!IsAttackable(bot_owner, bot_owner->GetTarget()) || target_type == BCEnum::TT_None) + return nullptr; + + auto target_mob = bot_owner->GetTarget(); + 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_OwnerRaid, + ABT_TargetGroup, + ABT_NamesGroup, + ABT_HealRotation, + ABT_HealRotationMembers, + ABT_HealRotationTargets, + ABT_Class, + ABT_Race, + 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_OwnerRaid = (1 << (ABT_OwnerRaid - 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_Class = (1 << (ABT_Class - 1)), + ABM_Race = (1 << (ABT_Race - 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_OwnerRaid | ABM_TargetGroup | ABM_NamesGroup | ABM_HealRotationTargets | ABM_Spawned | ABM_Class | ABM_Race), + ABM_Type2 = (ABM_ByName | ABM_OwnerGroup | ABM_OwnerRaid | ABM_NamesGroup | ABM_HealRotation | ABM_Spawned | ABM_Class | ABM_Race) + }; + + // Populates 'sbl' + static ABType PopulateSBL(Client* bot_owner, std::string ab_type_arg, std::list &sbl, int ab_mask, const char* name = nullptr, uint16 classrace = 0, 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("ownerraid")) { + ab_type = ABT_OwnerRaid; + } + 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("byclass")) { + ab_type = ABT_Class; + } + else if (!ab_type_arg.compare("byrace")) { + ab_type = ABT_Race; + } + 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_OwnerRaid: + if (ab_mask & ABM_OwnerRaid) { + MyBots::PopulateSBL_ByMyRaidBots(bot_owner, sbl, 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_Class: + if (ab_mask & ABM_Class) { + MyBots::PopulateSBL_BySpawnedBotsClass(bot_owner, sbl, classrace, clear_list); + } + break; + case ABT_Race: + if (ab_mask & ABM_Race) { + MyBots::PopulateSBL_BySpawnedBotsRace(bot_owner, sbl, classrace, 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(Chat::White, "Command passed null 'ActionableBot' criteria"); + } + else if (ab_mask & ab_type) { + if (classrace) { + bot_owner->Message(Chat::White, "You have no spawned bots meeting this criteria - type: '%s', %s: '%i'", ab_type_arg.c_str(), ab_mask & ABM_Class ? "class" : ab_mask & ABM_Race ? "race" : "", classrace); + } + else { + bot_owner->Message(Chat::White, "You have no spawned bots meeting this criteria - type: '%s', name: '%s'", ab_type_arg.c_str(), ((name) ? (name) : (""))); + } + } + else { + bot_owner->Message(Chat::White, "This command does not allow 'ActionableBot' criteria '%s'", ab_type_arg.c_str()); + } + + return ABT_None; + } + + return ab_type; + } + + 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() && !bot_grouped_player->GetRaid()) { + return nullptr; + } + + if (bot_owner->IsRaidGrouped()) { + Raid* raid = bot_grouped_player->GetRaid(); + if (!raid) { + return nullptr; + } + + uint32 raid_group = raid->GetGroup(bot_grouped_player); + if (raid_group >= MAX_RAID_GROUPS) { + return nullptr; + } + + for (const auto& m : raid->members) { + if (m.member && m.group_number == raid_group) { + if (!MyBots::IsMyBot(bot_owner, m.member->CastToBot())) { + continue; + } + + if (m.member->GetClass() != cls) { + continue; + } + + if (petless && m.member->GetPet()) { + continue; + } + + return static_cast(m.member->CastToBot()); + } + } + } + else { + 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, EQ::skills::SkillType 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 = EQ::invslot::EQUIPMENT_BEGIN; index <= EQ::invslot::EQUIPMENT_END; ++index) { + const EQ::ItemInstance* 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() != Class::Rogue && l->GetClass() != Class::Bard); }); + sbl.remove_if([bot_owner](const Bot* l) { return (l->GetClass() == Class::Rogue && l->GetLevel() < 5); }); + sbl.remove_if([bot_owner](const Bot* l) { return (l->GetClass() == Class::Bard && l->GetLevel() < 40); }); + + ActionableBots::Filter_ByHighestSkill(bot_owner, sbl, EQ::skills::SkillPickLock, pick_lock_value); + } +} + class STBaseEntry; class STCharmEntry; @@ -602,67 +1689,67 @@ void bot_command_view_combos(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_stop_melee_level(Client *c, const Seperator *sep); -void bot_subcommand_bot_suffix(Client *c, const Seperator *sep); -void bot_subcommand_bot_summon(Client *c, const Seperator *sep); -void bot_subcommand_bot_surname(Client *c, const Seperator *sep); -void bot_subcommand_bot_tattoo(Client *c, const Seperator *sep); -void bot_subcommand_bot_title(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_command_appearance(Client *c, const Seperator *sep); +void bot_command_beard_color(Client *c, const Seperator *sep); +void bot_command_beard_style(Client *c, const Seperator *sep); +void bot_command_camp(Client *c, const Seperator *sep); +void bot_command_clone(Client *c, const Seperator *sep); +void bot_command_create(Client *c, const Seperator *sep); +void bot_command_delete(Client *c, const Seperator *sep); +void bot_command_details(Client *c, const Seperator *sep); +void bot_command_dye_armor(Client *c, const Seperator *sep); +void bot_command_eyes(Client *c, const Seperator *sep); +void bot_command_face(Client *c, const Seperator *sep); +void bot_command_follow_distance(Client *c, const Seperator *sep); +void bot_command_hair_color(Client *c, const Seperator *sep); +void bot_command_hairstyle(Client *c, const Seperator *sep); +void bot_command_heritage(Client *c, const Seperator *sep); +void bot_command_inspect_message(Client *c, const Seperator *sep); +void bot_command_list_bots(Client *c, const Seperator *sep); +void bot_command_out_of_combat(Client *c, const Seperator *sep); +void bot_command_report(Client *c, const Seperator *sep); +void bot_command_spawn(Client *c, const Seperator *sep); +void bot_command_stance(Client *c, const Seperator *sep); +void bot_command_stop_melee_level(Client *c, const Seperator *sep); +void bot_command_suffix(Client *c, const Seperator *sep); +void bot_command_summon(Client *c, const Seperator *sep); +void bot_command_surname(Client *c, const Seperator *sep); +void bot_command_tattoo(Client *c, const Seperator *sep); +void bot_command_title(Client *c, const Seperator *sep); +void bot_command_toggle_archer(Client *c, const Seperator *sep); +void bot_command_toggle_helm(Client *c, const Seperator *sep); +void bot_command_update(Client *c, const Seperator *sep); +void bot_command_woad(Client *c, const Seperator *sep); -void bot_subcommand_circle(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_hot(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_delete(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_save(Client *c, const Seperator *sep); -void bot_subcommand_heal_rotation_set_hot(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_inventory_window(Client *c, const Seperator *sep); -void bot_subcommand_pet_get_lost(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_command_circle(Client *c, const Seperator *sep); +void bot_command_heal_rotation_adaptive_targeting(Client *c, const Seperator *sep); +void bot_command_heal_rotation_add_member(Client *c, const Seperator *sep); +void bot_command_heal_rotation_add_target(Client *c, const Seperator *sep); +void bot_command_heal_rotation_adjust_critical(Client *c, const Seperator *sep); +void bot_command_heal_rotation_adjust_safe(Client *c, const Seperator *sep); +void bot_command_heal_rotation_casting_override(Client *c, const Seperator *sep); +void bot_command_heal_rotation_change_interval(Client *c, const Seperator *sep); +void bot_command_heal_rotation_clear_hot(Client *c, const Seperator *sep); +void bot_command_heal_rotation_clear_targets(Client *c, const Seperator *sep); +void bot_command_heal_rotation_create(Client *c, const Seperator *sep); +void bot_command_heal_rotation_delete(Client *c, const Seperator *sep); +void bot_command_heal_rotation_fast_heals(Client *c, const Seperator *sep); +void bot_command_heal_rotation_list(Client *c, const Seperator *sep); +void bot_command_heal_rotation_remove_member(Client *c, const Seperator *sep); +void bot_command_heal_rotation_remove_target(Client *c, const Seperator *sep); +void bot_command_heal_rotation_reset_limits(Client *c, const Seperator *sep); +void bot_command_heal_rotation_save(Client *c, const Seperator *sep); +void bot_command_heal_rotation_set_hot(Client *c, const Seperator *sep); +void bot_command_heal_rotation_start(Client *c, const Seperator *sep); +void bot_command_heal_rotation_stop(Client *c, const Seperator *sep); +void bot_command_inventory_give(Client *c, const Seperator *sep); +void bot_command_inventory_list(Client *c, const Seperator *sep); +void bot_command_inventory_remove(Client *c, const Seperator *sep); +void bot_command_inventory_window(Client *c, const Seperator *sep); +void bot_command_pet_get_lost(Client *c, const Seperator *sep); +void bot_command_pet_remove(Client *c, const Seperator *sep); +void bot_command_pet_set_type(Client *c, const Seperator *sep); +void bot_command_portal(Client *c, const Seperator *sep); // bot command helpers diff --git a/zone/bot_commands/actionable.cpp b/zone/bot_commands/actionable.cpp new file mode 100644 index 000000000..84965b57d --- /dev/null +++ b/zone/bot_commands/actionable.cpp @@ -0,0 +1,24 @@ +#include "../bot_command.h" + +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(Chat::White, "Actionable command arguments:"); + c->Message(Chat::White, "target - selects target as single bot .. use ^command [target] or imply by empty actionable argument"); + c->Message(Chat::White, "byname [name] - selects single bot by name"); + c->Message(Chat::White, "ownergroup - selects all bots in the owner's group"); + c->Message(Chat::White, "ownerraid - selects all bots in the owner's raid"); + c->Message(Chat::White, "targetgroup - selects all bots in target's group"); + c->Message(Chat::White, "namesgroup [name] - selects all bots in name's group"); + c->Message(Chat::White, "healrotation [name] - selects all member and target bots of a heal rotation where name is a member"); + c->Message(Chat::White, "healrotationmembers [name] - selects all member bots of a heal rotation where name is a member"); + c->Message(Chat::White, "healrotationtargets [name] - selects all target bots of a heal rotation where name is a member"); + c->Message(Chat::White, "byclass - selects all bots of the chosen class"); + c->Message(Chat::White, "byrace - selects all bots of the chosen rsce"); + c->Message(Chat::White, "spawned - selects all spawned bots"); + c->Message(Chat::White, "all - selects all spawned bots .. argument use indicates en masse database updating"); + c->Message(Chat::White, "You may only select your bots as actionable"); +} diff --git a/zone/bot_commands/aggressive.cpp b/zone/bot_commands/aggressive.cpp new file mode 100644 index 000000000..6a3881d98 --- /dev/null +++ b/zone/bot_commands/aggressive.cpp @@ -0,0 +1,91 @@ +#include "../bot_command.h" + +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( + Chat::White, + "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", + sep->arg[0] + ); + helper_send_usage_required_bots(c, BCEnum::SpT_Stance); + return; + } + const int ab_mask = ActionableBots::ABM_Type1; + + std::string class_race_arg = sep->arg[1]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL( + c, + sep->arg[1], + sbl, + ab_mask, + !class_race_check ? sep->arg[2] : nullptr, + class_race_check ? atoi(sep->arg[2]) : 0 + ) == 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, + fmt::format( + "Using {}.", + spells[local_entry->spell_id].name + ).c_str() + ); + } + + my_bot->UseDiscipline(local_entry->spell_id, my_bot->GetID()); + ++success_count; + + bot_iter = sbl.erase(bot_iter); + } + } + + c->Message( + Chat::White, + "%i of %i bots have attempted to use aggressive disciplines", + success_count, + candidate_count + ); +} diff --git a/zone/bot_commands/appearance.cpp b/zone/bot_commands/appearance.cpp new file mode 100644 index 000000000..fcaec025c --- /dev/null +++ b/zone/bot_commands/appearance.cpp @@ -0,0 +1,534 @@ +#include "../bot_command.h" + +void bot_command_appearance(Client *c, const Seperator *sep) +{ + + std::list subcommand_list; + subcommand_list.push_back("botbeardcolor"); + subcommand_list.push_back("botbeardstyle"); + subcommand_list.push_back("botdetails"); + subcommand_list.push_back("botdyearmor"); + subcommand_list.push_back("boteyes"); + subcommand_list.push_back("botface"); + subcommand_list.push_back("bothaircolor"); + subcommand_list.push_back("bothairstyle"); + subcommand_list.push_back("botheritage"); + subcommand_list.push_back("bottattoo"); + subcommand_list.push_back("botwoad"); + + if (helper_command_alias_fail(c, "bot_command_appearance", sep->arg[0], "botappearance")) + return; + + helper_send_available_subcommands(c, "bot appearance", subcommand_list); +} + +void bot_command_beard_color(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_beard_color", sep->arg[0], "botbeardcolor")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [value: 0-n] (Dwarves or male bots only)", sep->arg[0]); + c->Message(Chat::White, "note: Actual limit is filter-based"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must a bot that you own to use this command"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "A numeric [value] is required to use this command"); + return; + } + + uint8 uvalue = Strings::ToInt(sep->arg[1]); + + auto fail_type = BCEnum::AFT_None; + if (my_bot->GetGender() != Gender::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_command_beard_style(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_beard_style", sep->arg[0], "botbeardstyle")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [value: 0-n] (Dwarves or male bots only)", sep->arg[0]); + c->Message(Chat::White, "note: Actual limit is filter-based"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must a bot that you own to use this command"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "A numeric [value] is required to use this command"); + return; + } + + uint8 uvalue = Strings::ToInt(sep->arg[1]); + + auto fail_type = BCEnum::AFT_None; + if (my_bot->GetGender() != Gender::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_command_details(Client *c, const Seperator *sep) +{ + // TODO: Trouble-shoot model update issue + + if (helper_command_alias_fail(c, "bot_command_details", sep->arg[0], "botdetails")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [value: 0-n] (Drakkin bots only)", sep->arg[0]); + c->Message(Chat::White, "note: Actual limit is filter-based"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must a bot that you own to use this command"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "A numeric [value] is required to use this command"); + return; + } + + uint32 uvalue = Strings::ToInt(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_command_dye_armor(Client *c, const Seperator *sep) +{ + // TODO: Trouble-shoot model update issue + + const std::string material_slot_message = fmt::format( + "Material Slots: * (All), {} (Head), {} (Chest), {} (Arms), {} (Wrists), {} (Hands), {} (Legs), {} (Feet)", + EQ::textures::armorHead, + EQ::textures::armorChest, + EQ::textures::armorArms, + EQ::textures::armorWrist, + EQ::textures::armorHands, + EQ::textures::armorLegs, + EQ::textures::armorFeet + ); + + if (helper_command_alias_fail(c, "bot_command_dye_armor", sep->arg[0], "botdyearmor")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1]) || !sep->arg[1] || (sep->arg[1] && !Strings::IsNumber(sep->arg[1]))) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Material Slot] [Red: 0-255] [Green: 0-255] [Blue: 0-255] ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", + sep->arg[0] + ).c_str() + ); + c->Message(Chat::White, material_slot_message.c_str()); + return; + } + const int ab_mask = ActionableBots::ABM_NoFilter; + + uint8 material_slot = EQ::textures::materialInvalid; + int16 slot_id = INVALID_INDEX; + + bool dye_all = (sep->arg[1][0] == '*'); + if (!dye_all) { + material_slot = Strings::ToInt(sep->arg[1]); + slot_id = EQ::InventoryProfile::CalcSlotFromMaterial(material_slot); + + if (!sep->IsNumber(1) || slot_id == INVALID_INDEX || material_slot > EQ::textures::LastTintableTexture) { + c->Message(Chat::White, "Valid material slots for this command are:"); + c->Message(Chat::White, material_slot_message.c_str()); + return; + } + } + + if (!sep->IsNumber(2)) { + c->Message(Chat::White, "Valid Red values for this command are 0 to 255."); + return; + } + + uint32 red_value = Strings::ToUnsignedInt(sep->arg[2]); + if (red_value > 255) { + red_value = 255; + } + + if (!sep->IsNumber(3)) { + c->Message(Chat::White, "Valid Green values for this command are 0 to 255."); + return; + } + + uint32 green_value = Strings::ToUnsignedInt(sep->arg[3]); + if (green_value > 255) { + green_value = 255; + } + + if (!sep->IsNumber(4)) { + c->Message(Chat::White, "Valid Blue values for this command are 0 to 255."); + return; + } + + uint32 blue_value = Strings::ToUnsignedInt(sep->arg[4]); + if (blue_value > 255) { + blue_value = 255; + } + + uint32 rgb_value = ((uint32)red_value << 16) | ((uint32)green_value << 8) | ((uint32)blue_value); + + 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( + Chat::White, + fmt::format( + "Failed to change armor color for {} due to unknown cause.", + bot_iter->GetCleanName() + ).c_str() + ); + return; + } + } + + if (ab_type == ActionableBots::ABT_All) { + if (dye_all) { + database.botdb.SaveAllArmorColors(c->CharacterID(), rgb_value); + } else { + database.botdb.SaveAllArmorColorBySlot(c->CharacterID(), slot_id, rgb_value); + } + } +} + +void bot_command_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_command_eyes", sep->arg[0], "boteyes")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + //c->Message(Chat::White, "usage: %s [value:0-n] ([option: left | right])", sep->arg[0]); + c->Message(Chat::White, "usage: %s [value: 0-n]", sep->arg[0]); + c->Message(Chat::White, "note: Actual limit is filter-based"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must a bot that you own to use this command"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "A numeric [value] is required to use this command"); + return; + } + + uint8 uvalue = Strings::ToInt(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(Chat::White, "This feature will update the next time your bot is spawned"); + //helper_bot_appearance_form_final(c, my_bot); +} + +void bot_command_face(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_face", sep->arg[0], "botface")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [value: 0-n]", sep->arg[0]); + c->Message(Chat::White, "note: Actual limit is filter-based"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must a bot that you own to use this command"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "A numeric [value] is required to use this command"); + return; + } + + uint8 uvalue = Strings::ToInt(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_command_hair_color(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_hair_color", sep->arg[0], "bothaircolor")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [value: 0-n]", sep->arg[0]); + c->Message(Chat::White, "note: Actual limit is filter-based"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must a bot that you own to use this command"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "A numeric [value] is required to use this command"); + return; + } + + uint8 uvalue = Strings::ToInt(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_command_hairstyle(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_hairstyle", sep->arg[0], "bothairstyle")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [value: 0-n]", sep->arg[0]); + c->Message(Chat::White, "note: Actual limit is filter-based"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must a bot that you own to use this command"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "A numeric [value] is required to use this command"); + return; + } + + uint8 uvalue = Strings::ToInt(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_command_heritage(Client *c, const Seperator *sep) +{ + // TODO: Trouble-shoot model update issue + + if (helper_command_alias_fail(c, "bot_command_heritage", sep->arg[0], "botheritage")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [value: 0-n] (Drakkin bots only)", sep->arg[0]); + c->Message(Chat::White, "note: Actual limit is filter-based"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must a bot that you own to use this command"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "A numeric [value] is required to use this command"); + return; + } + + uint32 uvalue = Strings::ToInt(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_command_tattoo(Client *c, const Seperator *sep) +{ + // TODO: Trouble-shoot model update issue + + if (helper_command_alias_fail(c, "bot_command_tattoo", sep->arg[0], "bottattoo")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [value: 0-n] (Drakkin bots only)", sep->arg[0]); + c->Message(Chat::White, "note: Actual limit is filter-based"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must a bot that you own to use this command"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "A numeric [value] is required to use this command"); + return; + } + + uint32 uvalue = Strings::ToInt(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_command_woad(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_woad", sep->arg[0], "botwoad")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [value: 0-n] (Barbarian bots only)", sep->arg[0]); + c->Message(Chat::White, "note: Actual limit is filter-based"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must a bot that you own to use this command"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "A numeric [value] is required to use this command"); + return; + } + + uint8 uvalue = Strings::ToInt(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); +} diff --git a/zone/bot_commands/apply_poison.cpp b/zone/bot_commands/apply_poison.cpp new file mode 100644 index 000000000..cfffff2cb --- /dev/null +++ b/zone/bot_commands/apply_poison.cpp @@ -0,0 +1,104 @@ +#include "../bot_command.h" + +void bot_command_apply_poison(Client* c, const Seperator* sep) +{ + if (helper_command_disabled(c, RuleB(Bots, AllowApplyPoisonCommand), "applypoison")) { + return; + } + if (helper_command_alias_fail(c, "bot_command_apply_poison", sep->arg[0], "applypoison")) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + + c->Message(Chat::White, "usage: %s", sep->arg[0]); + return; + } + + Bot* my_rogue_bot = nullptr; + auto t = c->GetTarget(); + + if ( + t && + t->IsBot() && + t->CastToBot()->GetBotOwnerCharacterID() == c->CharacterID() && + t->GetClass() == Class::Rogue + ) { + my_rogue_bot = t->CastToBot(); + } + + if (!my_rogue_bot) { + + c->Message(Chat::White, "You must target a rogue bot that you own to use this command!"); + return; + } + if (my_rogue_bot->GetLevel() < 18) { + + c->Message( + Chat::White, + "Your rogue bot must be level 18 before %s can apply poison!", + (my_rogue_bot->GetGender() == Gender::Female ? "she" : "he")); + return; + } + + const auto poison_instance = c->GetInv().GetItem(EQ::invslot::slotCursor); + if (!poison_instance) { + + c->Message(Chat::White, "No item found on cursor!"); + return; + } + + auto poison_data = poison_instance->GetItem(); + if (!poison_data) { + + c->Message(Chat::White, "No data found for cursor item!"); + return; + } + + if (poison_data->ItemType == EQ::item::ItemTypePoison) { + + if ((~poison_data->Races) & GetPlayerRaceBit(my_rogue_bot->GetRace())) { + + c->Message(Chat::White, "Invalid race for weapon poison!"); + return; + } + + if (poison_data->Proc.Level2 > my_rogue_bot->GetLevel()) { + + c->Message(Chat::White, "This poison is too powerful for your intended target!"); + return; + } + + // generalized from client ApplyPoison handler + double ChanceRoll = zone->random.Real(0, 1); + uint16 poison_skill = 95 + ((my_rogue_bot->GetLevel() - 18) * 5); + if (poison_skill > 200) { + poison_skill = 200; + } + bool apply_poison_chance = (ChanceRoll < (.75 + poison_skill / 1000)); + + if (apply_poison_chance && my_rogue_bot->AddProcToWeapon( + poison_data->Proc.Effect, + false, + (my_rogue_bot->GetDEX() / 100) + 103, + POISON_PROC + )) { + c->Message( + Chat::White, + "Successfully applied %s to %s's weapon.", + poison_data->Name, + my_rogue_bot->GetCleanName()); + } else { + c->Message( + Chat::White, + "Failed to apply %s to %s's weapon.", + poison_data->Name, + my_rogue_bot->GetCleanName()); + } + + c->DeleteItemInInventory(EQ::invslot::slotCursor, 1, true); + } else { + + c->Message(Chat::White, "Item on cursor is not a weapon poison!"); + return; + } +} diff --git a/zone/bot_commands/apply_potion.cpp b/zone/bot_commands/apply_potion.cpp new file mode 100644 index 000000000..2d27713c3 --- /dev/null +++ b/zone/bot_commands/apply_potion.cpp @@ -0,0 +1,80 @@ +#include "../bot_command.h" + +void bot_command_apply_potion(Client* c, const Seperator* sep) +{ + if (helper_command_disabled(c, RuleB(Bots, AllowApplyPotionCommand), "applypotion")) { + return; + } + if (helper_command_alias_fail(c, "bot_command_apply_potion", sep->arg[0], "applypotion")) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + + c->Message(Chat::White, "usage: %s", sep->arg[0]); + return; + } + + Bot* my_bot = nullptr; + if (c->GetTarget() && c->GetTarget()->IsBot() && c->GetTarget()->CastToBot()->GetBotOwnerCharacterID() == c->CharacterID()) { + my_bot = c->GetTarget()->CastToBot(); + } + if (!my_bot) { + + c->Message(Chat::White, "You must target a bot that you own to use this command!"); + return; + } + + const auto potion_instance = c->GetInv().GetItem(EQ::invslot::slotCursor); + if (!potion_instance) { + + c->Message(Chat::White, "No item found on cursor!"); + return; + } + + auto potion_data = potion_instance->GetItem(); + if (!potion_data) { + + c->Message(Chat::White, "No data found for cursor item!"); + return; + } + + if (potion_data->ItemType == EQ::item::ItemTypePotion && potion_data->Click.Effect > 0) { + + if (RuleB(Bots, RestrictApplyPotionToRogue) && potion_data->Classes != player_class_bitmasks[Class::Rogue]) { + + c->Message(Chat::White, "This command is restricted to rogue poison potions only!"); + return; + } + if ((~potion_data->Races) & GetPlayerRaceBit(my_bot->GetRace())) { + + c->Message(Chat::White, "Invalid race for potion!"); + return; + } + if ((~potion_data->Classes) & GetPlayerClassBit(my_bot->GetClass())) { + + c->Message(Chat::White, "Invalid class for potion!"); + return; + } + + if (potion_data->Click.Level2 > my_bot->GetLevel()) { + + c->Message(Chat::White, "This potion is too powerful for your intended target!"); + return; + } + + // TODO: figure out best way to handle casting time/animation + if (my_bot->SpellFinished(potion_data->Click.Effect, my_bot, EQ::spells::CastingSlot::Item, 0)) { + c->Message(Chat::White, "Successfully applied %s to %s's buff effects.", potion_data->Name, my_bot->GetCleanName()); + } + else { + c->Message(Chat::White, "Failed to apply %s to %s's buff effects.", potion_data->Name, my_bot->GetCleanName()); + } + + c->DeleteItemInInventory(EQ::invslot::slotCursor, 1, true); + } + else { + + c->Message(Chat::White, "Item on cursor is not a potion!"); + return; + } +} diff --git a/zone/bot_commands/attack.cpp b/zone/bot_commands/attack.cpp new file mode 100644 index 000000000..3d57b4279 --- /dev/null +++ b/zone/bot_commands/attack.cpp @@ -0,0 +1,72 @@ +#include "../bot_command.h" + +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(Chat::White, "usage: %s [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | byclass | byrace | default: 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(Chat::White, "You must an enemy to use this command"); + return; + } + + std::string ab_arg(sep->arg[1]); + if (ab_arg.empty()) { + ab_arg = "spawned"; + } + + std::string class_race_arg(sep->arg[1]); + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, ab_arg.c_str(), sbl, ab_mask, !class_race_check ? sep->arg[2] : nullptr, class_race_check ? atoi(sep->arg[2]) : 0) == ActionableBots::ABT_None) { + return; + } + + size_t attacker_count = 0; + Bot *first_attacker = nullptr; + sbl.remove(nullptr); + for (auto bot_iter : sbl) { + + if (bot_iter->GetAppearance() != eaDead && bot_iter->GetBotStance() != EQ::constants::stancePassive) { + + if (!first_attacker) { + first_attacker = bot_iter; + } + ++attacker_count; + + bot_iter->SetAttackFlag(); + } + } + + if (attacker_count == 1 && first_attacker) { + Bot::BotGroupSay( + first_attacker, + fmt::format( + "Attacking {}.", + target_mob->GetCleanName() + ).c_str() + ); + } else { + c->Message( + Chat::White, + fmt::format( + "{} of your bots are attacking {}.", + sbl.size(), + target_mob->GetCleanName() + ).c_str() + ); + } +} diff --git a/zone/bot_commands/bind_affinity.cpp b/zone/bot_commands/bind_affinity.cpp new file mode 100644 index 000000000..e6d01fe4c --- /dev/null +++ b/zone/bot_commands/bind_affinity.cpp @@ -0,0 +1,41 @@ +#include "../bot_command.h" + +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(Chat::White, "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(Chat::White, "Successfully bound %s to this location", target_mob->GetCleanName()); + else + c->Message(Chat::White, "Failed to bind %s to this location", target_mob->GetCleanName()); + break; + } + + helper_no_available_bots(c, my_bot); +} diff --git a/zone/bot_commands/bot.cpp b/zone/bot_commands/bot.cpp new file mode 100644 index 000000000..a89047cbb --- /dev/null +++ b/zone/bot_commands/bot.cpp @@ -0,0 +1,1427 @@ +#include "../bot_command.h" + +void bot_command_bot(Client *c, const Seperator *sep) +{ + + std::list subcommand_list; + subcommand_list.push_back("botappearance"); + subcommand_list.push_back("botcamp"); + subcommand_list.push_back("botclone"); + subcommand_list.push_back("botcreate"); + subcommand_list.push_back("botdelete"); + subcommand_list.push_back("botfollowdistance"); + subcommand_list.push_back("botinspectmessage"); + subcommand_list.push_back("botlist"); + subcommand_list.push_back("botoutofcombat"); + subcommand_list.push_back("botreport"); + subcommand_list.push_back("botspawn"); + subcommand_list.push_back("botstance"); + subcommand_list.push_back("botstopmeleelevel"); + subcommand_list.push_back("botsummon"); + subcommand_list.push_back("bottogglearcher"); + subcommand_list.push_back("bottogglehelm"); + subcommand_list.push_back("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_camp(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_camp", sep->arg[0], "botcamp")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + return; + } + const int ab_mask = ActionableBots::ABM_Type1; + + std::string class_race_arg = sep->arg[1]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, !class_race_check ? sep->arg[2] : nullptr, class_race_check ? atoi(sep->arg[2]) : 0) == ActionableBots::ABT_None) { + return; + } + + for (auto bot_iter : sbl) + bot_iter->Camp(); +} + +void bot_command_clone(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_clone", sep->arg[0], "botclone")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [clone_name]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must target a bot that you own to use this command!"); + return; + } + + if (!my_bot->GetBotID()) { + c->Message( + Chat::White, + fmt::format( + "An unknown error has occured with {} (Bot ID {}).", + my_bot->GetCleanName(), + my_bot->GetBotID() + ).c_str() + ); + LogCommands( + "bot_command_clone(): - Error: Active bot reported invalid ID (BotName: [{}], BotID: [{}], OwnerName: [{}], OwnerID: [{}], AcctName: [{}], AcctID: [{}])", + 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(Chat::White, "You must name your bot clone."); + return; + } + + std::string bot_name = sep->arg[1]; + + if (!Bot::IsValidName(bot_name)) { + c->Message( + Chat::White, + fmt::format( + "'{}' is an invalid name. You may only use characters 'A-Z', 'a-z' and '_'.", + bot_name + ).c_str() + ); + return; + } + + bool available_flag = false; + if (!database.botdb.QueryNameAvailablity(bot_name, available_flag)) { + c->Message( + Chat::White, + fmt::format( + "Failed to query name availability for '{}'.", + bot_name + ).c_str() + ); + return; + } + + if (!available_flag) { + c->Message( + Chat::White, + fmt::format( + "The name '{}' is already being used. Please choose a different name.", + bot_name + ).c_str() + ); + return; + } + + auto bot_creation_limit = c->GetBotCreationLimit(); + auto bot_creation_limit_class = c->GetBotCreationLimit(my_bot->GetClass()); + + uint32 bot_count = 0; + uint32 bot_class_count = 0; + if (!database.botdb.QueryBotCount(c->CharacterID(), my_bot->GetClass(), bot_count, bot_class_count)) { + c->Message(Chat::White, "Failed to query bot count."); + return; + } + + if (bot_creation_limit >= 0 && bot_count >= bot_creation_limit) { + std::string message; + + if (bot_creation_limit) { + message = fmt::format( + "You have reached the maximum limit of {} bot{}.", + bot_creation_limit, + bot_creation_limit != 1 ? "s" : "" + ); + } else { + message = "You cannot create any bots."; + } + + c->Message(Chat::White, message.c_str()); + return; + } + + if (bot_creation_limit_class >= 0 && bot_class_count >= bot_creation_limit_class) { + std::string message; + + if (bot_creation_limit_class) { + message = fmt::format( + "You cannot create anymore than {} {} bot{}.", + bot_creation_limit_class, + GetClassIDName(my_bot->GetClass()), + bot_creation_limit_class != 1 ? "s" : "" + ); + } else { + message = fmt::format( + "You cannot create any {} bots.", + GetClassIDName(my_bot->GetClass()) + ); + } + + c->Message(Chat::White, message.c_str()); + return; + } + + uint32 clone_id = 0; + if (!database.botdb.CreateCloneBot(my_bot->GetBotID(), bot_name, clone_id) || !clone_id) { + c->Message( + Chat::White, + fmt::format( + "Failed to create clone bot '{}'.", + bot_name + ).c_str() + ); + return; + } + + int clone_stance = EQ::constants::stancePassive; + if (!database.botdb.LoadStance(my_bot->GetBotID(), clone_stance)) { + c->Message( + Chat::White, + fmt::format( + "Failed to load stance from '{}'.", + my_bot->GetCleanName() + ).c_str() + ); + } + + if (!database.botdb.SaveStance(clone_id, clone_stance)) { + c->Message( + Chat::White, + fmt::format( + "Failed to save stance for clone '{}'.", + bot_name + ).c_str() + ); + } + + if (!database.botdb.CreateCloneBotInventory(my_bot->GetBotID(), clone_id)) { + c->Message( + Chat::White, + fmt::format( + "Failed to create clone bot inventory for clone '{}'.", + bot_name + ).c_str() + ); + } + + c->Message( + Chat::White, + fmt::format( + "Bot Cloned | From: {} To: {}", + my_bot->GetCleanName(), + bot_name + ).c_str() + ); +} + +void bot_command_create(Client *c, const Seperator *sep) +{ + const std::string class_substrs[17] = { + "", + "WAR", "CLR", "PAL", "RNG", + "SHD", "DRU", "MNK", "BRD", + "ROG", "SHM", "NEC", "WIZ", + "MAG", "ENC", "BST", "BER" + }; + + const std::string race_substrs[17] = { + "", + "HUM", "BAR", "ERU", "ELF", + "HIE", "DEF", "HEF", "DWF", + "TRL", "OGR", "HFL", "GNM", + "IKS", "VAH", "FRG", "DRK" + }; + + const uint16 race_values[17] = { + Race::Doug, + Race::Human, Race::Barbarian, Race::Erudite, Race::WoodElf, + Race::HighElf, Race::DarkElf, Race::HalfElf, Race::Dwarf, + Race::Troll, Race::Ogre, Race::Halfling, Race::Gnome, + Race::Iksar, Race::VahShir, Race::Froglok2, Race::Drakkin + }; + + const std::string gender_substrs[2] = { + "Male", "Female", + }; + + if (helper_command_alias_fail(c, "bot_command_create", sep->arg[0], "botcreate")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Name] [Class] [Race] [Gender]", + sep->arg[0] + ).c_str() + ); + + std::string window_text; + std::string message_separator; + int object_count = 0; + const int object_max = 4; + + window_text.append( + fmt::format( + "Classes{}", + DialogueWindow::Break() + ) + ); + + message_separator = " "; + object_count = 0; + for (int i = 0; i <= 15; ++i) { + window_text.append(message_separator); + + if (object_count >= object_max) { + window_text.append(DialogueWindow::Break()); + object_count = 0; + } + + window_text.append( + fmt::format("{} ({})", + class_substrs[i + 1], + (i + 1) + ) + ); + + ++object_count; + message_separator = ", "; + } + + window_text.append(DialogueWindow::Break(2)); + + window_text.append( + fmt::format( + "Races{}", + DialogueWindow::Break() + ) + ); + + message_separator = " "; + object_count = 0; + for (int i = 0; i <= 15; ++i) { + window_text.append(message_separator); + + if (object_count >= object_max) { + window_text.append(DialogueWindow::Break()); + object_count = 0; + } + + window_text.append( + fmt::format("{} ({})", + race_substrs[i + 1], + race_values[i + 1] + ) + ); + + ++object_count; + message_separator = ", "; + } + + window_text.append(DialogueWindow::Break(2)); + + window_text.append( + fmt::format( + "Genders{}", + DialogueWindow::Break() + ) + ); + + message_separator = " "; + for (int i = 0; i <= 1; ++i) { + window_text.append(message_separator); + + window_text.append( + fmt::format("{} ({})", + gender_substrs[i], + i + ) + ); + + message_separator = ", "; + } + + c->SendPopupToClient("Bot Creation Options", window_text.c_str()); + + return; + } + + const auto arguments = sep->argnum; + if (!arguments || sep->IsNumber(1)) { + c->Message(Chat::White, "You must name your bot!"); + return; + } + + std::string bot_name = sep->arg[1]; + bot_name = Strings::UcFirst(bot_name); + + if (arguments < 2 || !sep->IsNumber(2)) { + c->Message(Chat::White, "Invalid class!"); + return; + } + + auto bot_class = static_cast(Strings::ToUnsignedInt(sep->arg[2])); + + if (arguments < 3 || !sep->IsNumber(3)) { + c->Message(Chat::White, "Invalid race!"); + return; + } + + auto bot_race = static_cast(Strings::ToUnsignedInt(sep->arg[3])); + + if (arguments < 4) { + c->Message(Chat::White, "Invalid gender!"); + return; + } + + auto bot_gender = Gender::Male; + + if (sep->IsNumber(4)) { + bot_gender = static_cast(Strings::ToUnsignedInt(sep->arg[4])); + if (bot_gender == Gender::Neuter) { + bot_gender = Gender::Male; + } + } else { + if (!strcasecmp(sep->arg[4], "m") || !strcasecmp(sep->arg[4], "male")) { + bot_gender = Gender::Male; + } else if (!strcasecmp(sep->arg[4], "f") || !strcasecmp(sep->arg[4], "female")) { + bot_gender = Gender::Female; + } + } + + helper_bot_create(c, bot_name, bot_class, bot_race, bot_gender); +} + +void bot_command_delete(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_delete", sep->arg[0], "botdelete")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s", sep->arg[0]); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must a bot that you own to use this command"); + return; + } + + if (!my_bot->DeleteBot()) { + c->Message(Chat::White, "Failed to delete '%s' due to database error", my_bot->GetCleanName()); + return; + } + + auto bid = my_bot->GetBotID(); + std::string bot_name = my_bot->GetCleanName(); + + my_bot->Camp(false); + + c->Message(Chat::White, "Successfully deleted bot '%s' (id: %i)", bot_name.c_str(), bid); +} + +void bot_command_follow_distance(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_follow_distance", sep->arg[0], "botfollowdistance")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [set] [distance] ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "usage: %s [clear] ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + return; + } + const int ab_mask = ActionableBots::ABM_NoFilter; + + uint32 bfd = BOT_FOLLOW_DISTANCE_DEFAULT; + bool set_flag = false; + int ab_arg = 2; + + if (!strcasecmp(sep->arg[1], "set")) { + if (!sep->IsNumber(2)) { + c->Message(Chat::White, "A numeric [distance] is required to use this command"); + return; + } + + bfd = Strings::ToInt(sep->arg[2]); + if (bfd < 1) + bfd = 1; + if (bfd > BOT_FOLLOW_DISTANCE_DEFAULT_MAX) + bfd = BOT_FOLLOW_DISTANCE_DEFAULT_MAX; + set_flag = true; + ab_arg = 3; + } + else if (strcasecmp(sep->arg[1], "clear")) { + c->Message(Chat::White, "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 && !database.botdb.SaveFollowDistance(bot_iter->GetBotID(), bfd)) { + return; + } + + ++bot_count; + } + + if (ab_type == ActionableBots::ABT_All) { + if (!database.botdb.SaveAllFollowDistances(c->CharacterID(), bfd)) { + return; + } + + c->Message(Chat::White, "%s all of your bot follow distances", set_flag ? "Set" : "Cleared"); + } + else { + c->Message(Chat::White, "%s %i of your spawned bot follow distances", (set_flag ? "Set" : "Cleared"), bot_count); + } +} + +void bot_command_inspect_message(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_inspect_message", sep->arg[0], "botinspectmessage")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [set | clear] ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "Notes:"); + if (c->ClientVersion() >= EQ::versions::ClientVersion::SoF) { + c->Message(Chat::White, "- Self-inspect and type your bot's inspect message"); + c->Message(Chat::White, "- Close the self-inspect window to update the server"); + c->Message(Chat::White, "- Type '%s set' to set the bot's inspect message", sep->arg[0]); + } + else { + c->Message(Chat::White, "- Self-inspect and type your bot's inspect message"); + c->Message(Chat::White, "- Close the self-inspect window"); + c->Message(Chat::White, "- Self-inspect again to update the server"); + c->Message(Chat::White, "- 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(Chat::White, "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 client_message_struct = &c->GetInspectMessage(); + + int bot_count = 0; + for (auto bot_iter : sbl) { + if (!bot_iter) + continue; + + auto bot_message_struct = &bot_iter->GetInspectMessage(); + memset(bot_message_struct, 0, sizeof(InspectMessage_Struct)); + if (set_flag) + memcpy(bot_message_struct, client_message_struct, sizeof(InspectMessage_Struct)); + + if (ab_type != ActionableBots::ABT_All && !database.botdb.SaveInspectMessage(bot_iter->GetBotID(), *bot_message_struct)) { + return; + } + + ++bot_count; + } + + if (ab_type == ActionableBots::ABT_All) { + InspectMessage_Struct bot_message_struct; + memset(&bot_message_struct, 0, sizeof(InspectMessage_Struct)); + if (set_flag) + memcpy(&bot_message_struct, client_message_struct, sizeof(InspectMessage_Struct)); + + if (!database.botdb.SaveAllInspectMessages(c->CharacterID(), bot_message_struct)) { + return; + } + + c->Message(Chat::White, "%s all of your bot inspect messages", set_flag ? "Set" : "Cleared"); + } + else { + c->Message(Chat::White, "%s %i of your spawned bot inspect messages", set_flag ? "Set" : "Cleared", bot_count); + } +} + +void bot_command_list_bots(Client *c, const Seperator *sep) +{ + enum { + FilterClass, + FilterRace, + FilterName, + FilterCount, + MaskClass = (1 << FilterClass), + MaskRace = (1 << FilterRace), + MaskName = (1 << FilterName) + }; + + if (helper_command_alias_fail(c, "bot_command_list", sep->arg[0], "botlist")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} (account) ([class] [value]) ([race] [value]) ([name] [partial-full])", + sep->arg[0] + ).c_str() + ); + c->Message(Chat::White, "Note: Filter criteria is orderless and optional."); + return; + } + + bool Account = false; + int seps = 1; + uint32 filter_value[FilterCount]; + int name_criteria_arg = 0; + memset(&filter_value, 0, sizeof(uint32) * FilterCount); + + int filter_mask = 0; + if (!strcasecmp(sep->arg[1], "account")) { + Account = true; + seps = 2; + } + + for (int i = seps; i < (FilterCount * 2); i += 2) { + if (sep->arg[i][0] == '\0') { + break; + } + + if (!strcasecmp(sep->arg[i], "class")) { + filter_mask |= MaskClass; + filter_value[FilterClass] = Strings::ToInt(sep->arg[i + 1]); + continue; + } + + if (!strcasecmp(sep->arg[i], "race")) { + filter_mask |= MaskRace; + filter_value[FilterRace] = Strings::ToInt(sep->arg[i + 1]); + continue; + } + + if (!strcasecmp(sep->arg[i], "name")) { + filter_mask |= MaskName; + name_criteria_arg = (i + 1); + continue; + } + + c->Message(Chat::White, "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::list bots_list; + if (!database.botdb.LoadBotsList(c->CharacterID(), bots_list, Account)) { + return; + } + + if (bots_list.empty()) { + c->Message(Chat::White, "You have no bots."); + return; + } + + auto bot_count = 0; + auto bots_owned = 0; + auto bot_number = 1; + for (auto bots_iter : bots_list) { + if (filter_mask) { + if ((filter_mask & MaskClass) && filter_value[FilterClass] != bots_iter.class_) { + continue; + } + + if ((filter_mask & MaskRace) && filter_value[FilterRace] != bots_iter.race) { + 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 = bots_iter.bot_name; + std::transform(name_check.begin(), name_check.end(), name_check.begin(), ::tolower); + if (name_check.find(name_criteria) == std::string::npos) { + continue; + } + } + } + + auto* bot = entity_list.GetBotByBotName(bots_iter.bot_name); + + c->Message( + Chat::White, + fmt::format( + "Bot {} | {} is a Level {} {} {} {} owned by {}.", + bot_number, + ( + (c->CharacterID() == bots_iter.owner_id && !bot) ? + Saylink::Silent( + fmt::format("^spawn {}", bots_iter.bot_name), + bots_iter.bot_name + ) : + bots_iter.bot_name + ), + bots_iter.level, + GetGenderName(bots_iter.gender), + GetRaceIDName(bots_iter.race), + GetClassIDName(bots_iter.class_), + bots_iter.owner_name + ).c_str() + ); + + if (c->CharacterID() == bots_iter.owner_id) { + bots_owned++; + } + + bot_count++; + bot_number++; + } + + if (!bot_count) { + c->Message(Chat::White, "You have no bots meeting this criteria."); + return; + } else { + c->Message( + Chat::White, + fmt::format( + "{} Of {} bot{} shown, {} {} owned by you.", + bot_count, + bots_list.size(), + bot_count != 1 ? "s" : "", + bots_owned, + bots_owned != 1 ? "are" : "is" + ).c_str() + ); + + c->Message(Chat::White, "Note: You can spawn any owned bots by clicking their name if they are not already spawned."); + + c->Message(Chat::White, "Your bot creation limits are as follows:"); + + const auto overall_bot_creation_limit = c->GetBotCreationLimit(); + + c->Message( + Chat::White, + fmt::format( + "Overall | {} Bot{}", + overall_bot_creation_limit, + overall_bot_creation_limit != 1 ? "s" : "" + ).c_str() + ); + + for (uint8 class_id = Class::Warrior; class_id <= Class::Berserker; class_id++) { + auto class_creation_limit = c->GetBotCreationLimit(class_id); + + if (class_creation_limit != overall_bot_creation_limit) { + c->Message( + Chat::White, + fmt::format( + "{} | {} Bot{}", + GetClassIDName(class_id), + class_creation_limit, + class_creation_limit != 1 ? "s" : "" + ).c_str() + ); + } + } + } +} + +void bot_command_out_of_combat(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_out_of_combat", sep->arg[0], "botoutofcombat")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "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_command_report(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_report", sep->arg[0], "botreport")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | 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()) { + auto t = c->GetTarget(); + if (t && t->IsClient()) { + if (t->CastToClient() == c) { + ab_type_arg = "ownergroup"; + } else { + ab_type_arg = "targetgroup"; + } + } else { + ab_type_arg = "spawned"; + } + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, ab_type_arg, 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", GetClassIDName(bot_iter->GetClass()), 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(Chat::White, "%s", report_msg.c_str()); + } +} + +void bot_command_spawn(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_spawn", sep->arg[0], "botspawn")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [bot_name]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto bot_character_level = c->GetBotRequiredLevel(); + if ( + bot_character_level >= 0 && + c->GetLevel() < bot_character_level && + !c->GetGM() + ) { + c->Message( + Chat::White, + fmt::format( + "You must be level {} to spawn bots.", + bot_character_level + ).c_str() + ); + return; + } + + if (!Bot::CheckSpawnConditions(c)) { + return; + } + + auto bot_spawn_limit = c->GetBotSpawnLimit(); + auto spawned_bot_count = Bot::SpawnedBotCount(c->CharacterID()); + + if ( + bot_spawn_limit >= 0 && + spawned_bot_count >= bot_spawn_limit && + !c->GetGM() + ) { + std::string message; + if (bot_spawn_limit) { + message = fmt::format( + "You cannot have more than {} spawned bot{}.", + bot_spawn_limit, + bot_spawn_limit != 1 ? "s" : "" + ); + } else { + message = "You are not currently allowed to spawn any bots."; + } + + c->Message(Chat::White, message.c_str()); + return; + } + + if (sep->arg[1][0] == '\0' || sep->IsNumber(1)) { + c->Message(Chat::White, "You must specify a name to use this command."); + return; + } + + std::string bot_name = sep->arg[1]; + + uint32 bot_id = 0; + uint8 bot_class = Class::None; + if (!database.botdb.LoadBotID(bot_name, bot_id, bot_class)) { + c->Message( + Chat::White, + fmt::format( + "Failed to load bot ID for '{}'.", + bot_name + ).c_str() + ); + return; + } + + auto bot_spawn_limit_class = c->GetBotSpawnLimit(bot_class); + auto spawned_bot_count_class = Bot::SpawnedBotCount(c->CharacterID(), bot_class); + + if ( + bot_spawn_limit_class >= 0 && + spawned_bot_count_class >= bot_spawn_limit_class && + !c->GetGM() + ) { + std::string message; + + if (bot_spawn_limit_class) { + message = fmt::format( + "You cannot have more than {} spawned {} bot{}.", + bot_spawn_limit_class, + GetClassIDName(bot_class), + bot_spawn_limit_class != 1 ? "s" : "" + ); + } else { + message = fmt::format( + "You are not currently allowed to spawn any {} bots.", + GetClassIDName(bot_class) + ); + } + + c->Message(Chat::White, message.c_str()); + return; + } + + auto bot_character_level_class = c->GetBotRequiredLevel(bot_class); + if ( + bot_character_level_class >= 0 && + c->GetLevel() < bot_character_level_class && + !c->GetGM() + ) { + c->Message( + Chat::White, + fmt::format( + "You must be level {} to spawn {} bots.", + bot_character_level_class, + GetClassIDName(bot_class) + ).c_str() + ); + return; + } + + if (!bot_id) { + c->Message( + Chat::White, + fmt::format( + "You don't own a bot named '{}'.", + bot_name + ).c_str() + ); + return; + } + + if (entity_list.GetMobByBotID(bot_id)) { + c->Message( + Chat::White, + fmt::format( + "'{}' is already spawned.", + bot_name + ).c_str() + ); + return; + } + + auto my_bot = Bot::LoadBot(bot_id); + if (!my_bot) { + c->Message( + Chat::White, + fmt::format( + "Invalid bot '{}' (ID {})", + bot_name, + bot_id + ).c_str() + ); + return; + } + + if (!my_bot->Spawn(c)) { + c->Message( + Chat::White, + fmt::format( + "Failed to spawn '{}' (ID {})", + bot_name, + bot_id + ).c_str() + ); + safe_delete(my_bot); + return; + } + + static std::string bot_spawn_message[17] = { + "I am ready to fight!", // DEFAULT + "A solid weapon is my ally!", // Class::Warrior + "The pious shall never die!", // Class::Cleric + "I am the symbol of Light!", // Class::Paladin + "There are enemies near!", // Class::Ranger + "Out of the shadows, I step!", // Class::ShadowKnight + "Nature's fury shall be wrought!", // Class::Druid + "Your punishment will be my fist!", // Class::Monk + "Music is the overture of battle! ", // BARD + "Daggers into the backs of my enemies!", // Class::Rogue + "More bones to grind!", // Class::Shaman + "Death is only the beginning!", // Class::Necromancer + "I am the harbinger of demise!", // Class::Wizard + "The elements are at my command!", // Class::Magician + "No being can resist my charm!", // Class::Enchanter + "Battles are won by hand and paw!", // Class::Beastlord + "My bloodthirst shall not be quenched!" // Class::Berserker + }; + + uint8 message_index = 0; + if (c->GetBotOption(Client::booSpawnMessageClassSpecific)) { + message_index = VALIDATECLASSID(my_bot->GetClass()); + } + + if (c->GetBotOption(Client::booSpawnMessageSay)) { + Bot::BotGroupSay(my_bot, bot_spawn_message[message_index].c_str()); + } else if (c->GetBotOption(Client::booSpawnMessageTell)) { + my_bot->OwnerMessage(bot_spawn_message[message_index]); + } +} + +void bot_command_stance(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_stance", sep->arg[0], "botstance")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [current | value: 1-9] ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "value: %u(%s), %u(%s), %u(%s), %u(%s), %u(%s), %u(%s), %u(%s)", + EQ::constants::stancePassive, EQ::constants::GetStanceName(EQ::constants::stancePassive), + EQ::constants::stanceBalanced, EQ::constants::GetStanceName(EQ::constants::stanceBalanced), + EQ::constants::stanceEfficient, EQ::constants::GetStanceName(EQ::constants::stanceEfficient), + EQ::constants::stanceReactive, EQ::constants::GetStanceName(EQ::constants::stanceReactive), + EQ::constants::stanceAggressive, EQ::constants::GetStanceName(EQ::constants::stanceAggressive), + EQ::constants::stanceAssist, EQ::constants::GetStanceName(EQ::constants::stanceAssist), + EQ::constants::stanceBurn, EQ::constants::GetStanceName(EQ::constants::stanceBurn), + EQ::constants::stanceEfficient2, EQ::constants::GetStanceName(EQ::constants::stanceEfficient2), + EQ::constants::stanceBurnAE, EQ::constants::GetStanceName(EQ::constants::stanceBurnAE) + ); + return; + } + int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); + + bool current_flag = false; + auto bst = EQ::constants::stanceUnknown; + + if (!strcasecmp(sep->arg[1], "current")) + current_flag = true; + else if (sep->IsNumber(1)) { + bst = (EQ::constants::StanceType)Strings::ToInt(sep->arg[1]); + if (bst < EQ::constants::stanceUnknown || bst > EQ::constants::stanceBurnAE) + bst = EQ::constants::stanceUnknown; + } + + if (!current_flag && bst == EQ::constants::stanceUnknown) { + c->Message(Chat::White, "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->Save(); + } + + Bot::BotGroupSay( + bot_iter, + fmt::format( + "My current stance is {} ({}).", + EQ::constants::GetStanceName(bot_iter->GetBotStance()), + bot_iter->GetBotStance() + ).c_str() + ); + } +} + +void bot_command_stop_melee_level(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_stop_melee_level", sep->arg[0], "botstopmeleelevel")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [current | reset | sync | value: 0-255]", sep->arg[0]); + c->Message(Chat::White, "note: Only caster or hybrid class bots may be modified"); + c->Message(Chat::White, "note: Use [reset] to set stop melee level to server rule"); + c->Message(Chat::White, "note: Use [sync] to set stop melee level to current bot level"); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must a bot that you own to use this command"); + return; + } + if (!IsCasterClass(my_bot->GetClass()) && !IsHybridClass(my_bot->GetClass())) { + c->Message(Chat::White, "You must a caster or hybrid class bot to use this command"); + return; + } + + uint8 sml = RuleI(Bots, CasterStopMeleeLevel); + + if (sep->IsNumber(1)) { + sml = Strings::ToInt(sep->arg[1]); + } + else if (!strcasecmp(sep->arg[1], "sync")) { + sml = my_bot->GetLevel(); + } + else if (!strcasecmp(sep->arg[1], "current")) { + c->Message(Chat::White, "My current melee stop level is %u", my_bot->GetStopMeleeLevel()); + return; + } + else if (strcasecmp(sep->arg[1], "reset")) { + c->Message(Chat::White, "A [current] or [reset] argument, or numeric [value] is required to use this command"); + return; + } + // [reset] falls through with initialization value + + my_bot->SetStopMeleeLevel(sml); + database.botdb.SaveStopMeleeLevel(my_bot->GetBotID(), sml); + + c->Message(Chat::White, "Successfully set stop melee level for %s to %u", my_bot->GetCleanName(), sml); +} + +void bot_command_summon(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_summon", sep->arg[0], "botsummon")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + return; + } + const int ab_mask = ActionableBots::ABM_Type1; + + std::string class_race_arg = sep->arg[1]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, !class_race_check ? sep->arg[2] : nullptr, class_race_check ? atoi(sep->arg[2]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + for (auto bot_iter : sbl) { + if (!bot_iter) { + continue; + } + + bot_iter->WipeHateList(); + bot_iter->SetTarget(nullptr); + bot_iter->Teleport(c->GetPosition()); + bot_iter->DoAnim(0); + + if (!bot_iter->HasPet()) { + continue; + } + + bot_iter->GetPet()->WipeHateList(); + bot_iter->GetPet()->SetTarget(nullptr); + bot_iter->GetPet()->Teleport(c->GetPosition()); + } + + if (sbl.size() == 1) { + c->Message( + Chat::White, + fmt::format( + "Summoned {} to you.", + sbl.front() ? sbl.front()->GetCleanName() : "no one" + ).c_str() + ); + } + else { + c->Message( + Chat::White, + fmt::format( + "Summoned {} bots to you.", + sbl.size() + ).c_str() + ); + } +} + +void bot_command_toggle_archer(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_toggle_archer", sep->arg[0], "bottogglearcher")) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "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->SetBotArcherySetting(!bot_iter->IsBotArcher(), true); + } + else { + bot_iter->SetBotArcherySetting(archer_state, true); + } + bot_iter->ChangeBotArcherWeapons(bot_iter->IsBotArcher()); + + if (bot_iter->GetClass() == Class::Ranger && bot_iter->GetLevel() >= 61) { + bot_iter->SetRangerAutoWeaponSelect(bot_iter->IsBotArcher()); + } + } +} + +void bot_command_toggle_helm(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_toggle_helm", sep->arg[0], "bottogglehelm")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s ([option: on | off]) ([actionable: target | byname | ownergroup | 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 (!database.botdb.SaveHelmAppearance(bot_iter->GetBotID(), bot_iter->GetShowHelm())) { + 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 = AppearanceType::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) { + if (toggle_helm) { + database.botdb.ToggleAllHelmAppearances(c->CharacterID()); + } + else { + database.botdb.SaveAllHelmAppearances(c->CharacterID(), helm_state); + } + + c->Message(Chat::White, "%s all of your bot show helm flags", toggle_helm ? "Toggled" : (helm_state ? "Set" : "Cleared")); + } + else { + c->Message(Chat::White, "%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 AppearanceType::ShowHelm appearance type *** + */ +} + +void bot_command_update(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_update", sep->arg[0], "botupdate")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s", sep->arg[0]); + return; + } + + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + if (sbl.empty()) { + c->Message(Chat::White, "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(c->GetBotOption(Client::booStatsUpdate)); + bot_iter->SendAppearancePacket(AppearanceType::WhoLevel, bot_iter->GetLevel(), true, true); + ++bot_count; + } + + c->Message(Chat::White, "Updated %i of your %i spawned bots", bot_count, sbl.size()); +} diff --git a/zone/bot_commands/caster_range.cpp b/zone/bot_commands/caster_range.cpp new file mode 100644 index 000000000..2c1faf639 --- /dev/null +++ b/zone/bot_commands/caster_range.cpp @@ -0,0 +1,107 @@ +#include "../bot_command.h" + +void bot_command_caster_range(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_caster_range", sep->arg[0], "casterrange")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [current | value: 0 - 300] ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "note: Can only be used for Casters or Hybrids."); + c->Message(Chat::White, "note: Use [current] to check the current setting."); + c->Message(Chat::White, "note: Set the value to the minimum distance you want your bot to try to remain from its target."); + c->Message(Chat::White, "note: If they are too far for a spell, it will be skipped."); + c->Message(Chat::White, "note: This is set to (90) units by default."); + return; + } + const int ab_mask = ActionableBots::ABM_Type1; + + std::string arg1 = sep->arg[1]; + int ab_arg = 1; + bool current_check = false; + uint32 crange = 0; + + if (sep->IsNumber(1)) { + ab_arg = 2; + crange = atoi(sep->arg[1]); + if (crange < 0 || crange > 300) { + c->Message(Chat::White, "You must enter a value within the range of 0 - 300."); + return; + } + } + else if (!arg1.compare("current")) { + ab_arg = 2; + current_check = true; + } + else { + c->Message(Chat::White, "Incorrect argument, use %s help for a list of options.", sep->arg[0]); + return; + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + + Bot* first_found = nullptr; + int success_count = 0; + + for (auto my_bot : sbl) { + if (!IsCasterClass(my_bot->GetClass()) && !IsHybridClass(my_bot->GetClass())) { + continue; + } + + if (!first_found) { + first_found = my_bot; + } + + if (current_check) { + c->Message( + Chat::White, + fmt::format( + "{} says, 'My current Caster Range is {}.'", + my_bot->GetCleanName(), + my_bot->GetBotCasterRange() + ).c_str() + ); + } + else { + my_bot->SetBotCasterRange(crange); + ++success_count; + + database.botdb.SaveBotCasterRange(my_bot->GetBotID(), crange); + } + } + if (!current_check) { + if (success_count == 1 && first_found) { + c->Message( + Chat::White, + fmt::format( + "{} says, 'My Caster Range was set to {}.'", + first_found->GetCleanName(), + crange + ).c_str() + ); + } + else { + c->Message( + Chat::White, + fmt::format( + "{} of your bots set their Caster Range to {}.", + success_count, + crange + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/charm.cpp b/zone/bot_commands/charm.cpp new file mode 100644 index 000000000..2df4694b6 --- /dev/null +++ b/zone/bot_commands/charm.cpp @@ -0,0 +1,54 @@ +#include "../bot_command.h" + +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(Chat::White, "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(Chat::White, "Your is already charmed"); + return; + } + + if (spells[local_entry->spell_id].max_value[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); +} diff --git a/zone/bot_commands/click_item.cpp b/zone/bot_commands/click_item.cpp new file mode 100644 index 000000000..b236815d8 --- /dev/null +++ b/zone/bot_commands/click_item.cpp @@ -0,0 +1,56 @@ +#include "../bot_command.h" + +void bot_command_click_item(Client* c, const Seperator* sep) +{ + if (!RuleB(Bots, BotsCanClickItems)) { + c->Message(Chat::White, "The ability for bots to click equipped items is currently disabled."); + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "This will cause the selected bots to click the item in the given slot ID."); + c->Message(Chat::White, "Use ^invlist to see their items along with slot IDs."); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(Chat::Yellow, "You must specify a slot ID. Use %s help for more information.", sep->arg[0]); + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + + int ab_arg = 1; + uint32 slot_id = 0; + + if (sep->IsNumber(1)) { + ab_arg = 2; + slot_id = atoi(sep->arg[1]); + if (slot_id < EQ::invslot::EQUIPMENT_BEGIN || slot_id > EQ::invslot::EQUIPMENT_END) { + c->Message(Chat::Yellow, "You must specify a valid inventory slot from 0 to 22. Use %s help for more information", sep->arg[0]); + return; + } + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + sbl.remove(nullptr); + + for (auto my_bot : sbl) { + if (RuleI(Bots, BotsClickItemsMinLvl) > my_bot->GetLevel()) { + c->Message(Chat::White, "%s must be level %i to use clickable items.", my_bot->GetCleanName(), RuleI(Bots, BotsClickItemsMinLvl)); + continue; + } + + my_bot->TryItemClick(slot_id); + } +} diff --git a/zone/bot_commands/cure.cpp b/zone/bot_commands/cure.cpp new file mode 100644 index 000000000..4452f8088 --- /dev/null +++ b/zone/bot_commands/cure.cpp @@ -0,0 +1,71 @@ +#include "../bot_command.h" + +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(Chat::White, "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(Chat::White, "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); +} diff --git a/zone/bot_commands/defensive.cpp b/zone/bot_commands/defensive.cpp new file mode 100644 index 000000000..a2edc3e6e --- /dev/null +++ b/zone/bot_commands/defensive.cpp @@ -0,0 +1,69 @@ +#include "../bot_command.h" + +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(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Stance); + return; + } + const int ab_mask = ActionableBots::ABM_Type1; + + std::string class_race_arg = sep->arg[1]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[1], sbl, ab_mask, !class_race_check ? sep->arg[2] : nullptr, class_race_check ? atoi(sep->arg[2]) : 0) == 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_Defensive) + 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, + fmt::format( + "Using {}.", + spells[local_entry->spell_id].name + ).c_str() + ); + } + + my_bot->UseDiscipline(local_entry->spell_id, my_bot->GetID()); + ++success_count; + + bot_iter = sbl.erase(bot_iter); + } + } + + c->Message(Chat::White, "%i of %i bots have attempted to use defensive disciplines", success_count, candidate_count); +} diff --git a/zone/bot_commands/depart.cpp b/zone/bot_commands/depart.cpp new file mode 100644 index 000000000..b337ef404 --- /dev/null +++ b/zone/bot_commands/depart.cpp @@ -0,0 +1,59 @@ +#include "../bot_command.h" + +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(Chat::White, "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, Class::Druid); + Bot* my_wizard_bot = ActionableBots::AsGroupMember_ByClass(c, c, Class::Wizard); + helper_command_depart_list(c, my_druid_bot, my_wizard_bot, local_list, single); + return; + } + else if (destination.empty()) { + c->Message(Chat::White, "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); +} diff --git a/zone/bot_commands/escape.cpp b/zone/bot_commands/escape.cpp new file mode 100644 index 000000000..31fde82a6 --- /dev/null +++ b/zone/bot_commands/escape.cpp @@ -0,0 +1,44 @@ +#include "../bot_command.h" + +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(Chat::White, "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); +} diff --git a/zone/bot_commands/find_aliases.cpp b/zone/bot_commands/find_aliases.cpp new file mode 100644 index 000000000..db8fcbfc9 --- /dev/null +++ b/zone/bot_commands/find_aliases.cpp @@ -0,0 +1,42 @@ +#include "../bot_command.h" + +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(Chat::White, "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(Chat::White, "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(Chat::White, "An unknown condition has occurred..."); + return; + } + + c->Message(Chat::White, "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( + Chat::White, + fmt::format( + "^{}", + alias_iter.first + ).c_str() + ); + + ++bot_command_aliases_shown; + } + c->Message(Chat::White, "%d bot command alias%s listed.", bot_command_aliases_shown, bot_command_aliases_shown != 1 ? "es" : ""); +} diff --git a/zone/bot_commands/follow.cpp b/zone/bot_commands/follow.cpp new file mode 100644 index 000000000..65ec73f8b --- /dev/null +++ b/zone/bot_commands/follow.cpp @@ -0,0 +1,113 @@ +#include "../bot_command.h" + +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(Chat::White, "usage: () %s ([option: reset]) [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | byclass | byrace | spawned]] ([actionable_name])", sep->arg[0]); + c->Message(Chat::White, "usage: %s chain", 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 optional_arg = sep->arg[1]; + if (!optional_arg.compare("chain")) { + + auto chain_count = helper_bot_follow_option_chain(c); + c->Message(Chat::White, "%i of your bots %s now chain following you", chain_count, (chain_count == 1 ? "is" : "are")); + + return; + } + else if (!optional_arg.compare("reset")) { + reset = true; + ab_arg = 2; + name_arg = 3; + } + else { + target_mob = ActionableTarget::VerifyFriendly(c, BCEnum::TT_Single); + if (!target_mob) { + c->Message(Chat::White, "You must a friendly mob to use this command"); + return; + } + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[name_arg] : nullptr, class_race_check ? atoi(sep->arg[name_arg]) : 0) == 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()); + + bot_iter->SetManualFollow(false); + } + else { + if (bot_iter == target_mob) + bot_iter->SetFollowID(c->GetID()); + else + bot_iter->SetFollowID(target_mob->GetID()); + + bot_iter->SetManualFollow(true); + } + } + else { + bot_iter->SetFollowID(0); + bot_iter->SetManualFollow(false); + } + 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(), + fmt::format( + "Following {}.", + follow_mob ? follow_mob->GetCleanName() : "no one" + ).c_str() + ); + } else { + if (reset) { + c->Message( + Chat::White, + fmt::format( + "{} of your bots are following their default assignments.", + sbl.size() + ).c_str() + ); + } else { + c->Message( + Chat::White, + fmt::format( + "{} of your bots are following {}.", + sbl.size(), + target_mob->GetCleanName() + ).c_str() + ); + } + } +} diff --git a/zone/bot_commands/follow_distance.cpp b/zone/bot_commands/follow_distance.cpp new file mode 100644 index 000000000..dc2b7d868 --- /dev/null +++ b/zone/bot_commands/follow_distance.cpp @@ -0,0 +1 @@ +#include "../bot_command.h" diff --git a/zone/bot_commands/guard.cpp b/zone/bot_commands/guard.cpp new file mode 100644 index 000000000..27a9ed343 --- /dev/null +++ b/zone/bot_commands/guard.cpp @@ -0,0 +1,60 @@ +#include "../bot_command.h" + +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(Chat::White, "usage: %s ([option: clear]) [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | byclass | byrace | spawned]] ([actionable_name])", sep->arg[0]); + return; + } + const int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_Type2); + + bool clear = false; + int ab_arg = 1; + int name_arg = 2; + + std::string clear_arg = sep->arg[1]; + if (!clear_arg.compare("clear")) { + + clear = true; + ab_arg = 2; + name_arg = 3; + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[name_arg] : nullptr, class_race_check ? atoi(sep->arg[name_arg]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + for (auto bot_iter : sbl) { + + if (clear) { + bot_iter->SetGuardFlag(false); + } + else { + bot_iter->SetGuardMode(); + } + } + + if (sbl.size() == 1) { + Bot::BotGroupSay( + sbl.front(), + fmt::format( + "{}uarding this position.", + clear ? "No longer g" : "G" + ).c_str() + ); + } else { + c->Message(Chat::White, "%i of your bots are %sguarding their positions.", sbl.size(), (clear ? "no longer " : "")); + } +} diff --git a/zone/bot_commands/heal_rotation.cpp b/zone/bot_commands/heal_rotation.cpp new file mode 100644 index 000000000..b755016fc --- /dev/null +++ b/zone/bot_commands/heal_rotation.cpp @@ -0,0 +1,1422 @@ +#include "../bot_command.h" + +void bot_command_heal_rotation(Client *c, const Seperator *sep) +{ + + std::list subcommand_list; + subcommand_list.push_back("healrotationadaptivetargeting"); + subcommand_list.push_back("healrotationaddmember"); + subcommand_list.push_back("healrotationaddtarget"); + subcommand_list.push_back("healrotationadjustcritical"); + subcommand_list.push_back("healrotationadjustsafe"); + subcommand_list.push_back("healrotationcastoverride"); + subcommand_list.push_back("healrotationchangeinterval"); + subcommand_list.push_back("healrotationclearhot"); + subcommand_list.push_back("healrotationcleartargets"); + subcommand_list.push_back("healrotationcreate"); + subcommand_list.push_back("healrotationdelete"); + subcommand_list.push_back("healrotationfastheals"); + subcommand_list.push_back("healrotationlist"); + subcommand_list.push_back("healrotationremovemember"); + subcommand_list.push_back("healrotationremovetarget"); + subcommand_list.push_back("healrotationresetlimits"); + subcommand_list.push_back("healrotationsave"); + subcommand_list.push_back("healrotationsethot"); + subcommand_list.push_back("healrotationstart"); + subcommand_list.push_back("healrotationstop"); + + if (helper_command_alias_fail(c, "bot_command_heal_rotation", sep->arg[0], "healrotation")) + return; + +#if (EQDEBUG >= 12) + while (c->Admin() >= AccountStatus::GMImpossible) { + 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_heal_rotation_adaptive_targeting(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail( + c, + "bot_command_heal_rotation_adaptive_targeting", + sep->arg[0], + "healrotationadaptivetargeting" + )) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "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( + Chat::White, + "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(Chat::White, "%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( + Chat::White, + "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( + Chat::White, + "Adaptive targeting is now '%s' for %s's Heal Rotation", + (((*current_member->MemberOfHealRotation())->AdaptiveTargeting()) ? ("on") : ("off")), + current_member->GetCleanName()); +} + +void bot_command_heal_rotation_add_member(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_heal_rotation_add_member", sep->arg[0], "healrotationaddmember")) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "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(Chat::White, "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(Chat::White, "Error: New member bot dereferenced to nullptr"); + return; + } + + if (new_member->IsHealRotationMember()) { + c->Message( + Chat::White, + "%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( + Chat::White, + "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(Chat::White, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + if (!new_member->JoinHealRotationMemberPool(current_member->MemberOfHealRotation())) { + c->Message( + Chat::White, + "Failed to add %s as a current member of this Heal Rotation", + new_member->GetCleanName()); + return; + } + + c->Message( + Chat::White, + "Successfully added %s as a current member of this Heal Rotation", + new_member->GetCleanName()); +} + +void bot_command_heal_rotation_add_target(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_heal_rotation_add_target", sep->arg[0], "healrotationaddtarget")) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "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( + Chat::White, + "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(Chat::White, "%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(Chat::White, "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(Chat::White, "%s's entity type is not an allowable heal target", heal_target->GetCleanName()); + return; + } + + if (!heal_target->JoinHealRotationTargetPool(current_member->MemberOfHealRotation())) { + c->Message(Chat::White, "Failed to add heal target with a name of '%s'", heal_target->GetCleanName()); + return; + } + + c->Message( + Chat::White, + "Successfully added heal target %s to %s's Heal Rotation", + heal_target->GetCleanName(), + current_member->GetCleanName()); +} + +void bot_command_heal_rotation_adjust_critical(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail( + c, + "bot_command_heal_rotation_adjust_critical", + sep->arg[0], + "healrotationadjustcritical" + )) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message( + Chat::White, + "usage: () %s [armor_type] [value: %3.1f-%3.1f | + | -] ([member_name])", + sep->arg[0], + CRITICAL_HP_RATIO_BASE, + SAFE_HP_RATIO_BASE + ); + c->Message( + Chat::White, "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 = Strings::ToInt(armor_type_arg); + } + + if (armor_type_value > ARMOR_TYPE_LAST) { + c->Message( + Chat::White, + "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( + Chat::White, + "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(Chat::White, "%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 = Strings::ToFloat(critical_arg); + } 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_ABS) { + critical_ratio = SAFE_HP_RATIO_ABS; + } + if (critical_ratio < CRITICAL_HP_RATIO_ABS) { + critical_ratio = CRITICAL_HP_RATIO_ABS; + } + + if (!(*current_member->MemberOfHealRotation())->SetArmorTypeCriticalHPRatio(armor_type_value, critical_ratio)) { + c->Message( + Chat::White, + "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( + Chat::White, + "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_command_heal_rotation_adjust_safe(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_heal_rotation_adjust_safe", sep->arg[0], "healrotationadjustsafe")) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message( + Chat::White, + "usage: () %s [armor_type] [value: %3.1f-%3.1f | + | -] ([member_name])", + sep->arg[0], + CRITICAL_HP_RATIO_BASE, + SAFE_HP_RATIO_BASE + ); + c->Message( + Chat::White, "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 = Strings::ToInt(armor_type_arg); + } + + if (armor_type_value > ARMOR_TYPE_LAST) { + c->Message( + Chat::White, + "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( + Chat::White, + "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(Chat::White, "%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 = Strings::ToFloat(safe_arg); + } 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_ABS) { + safe_ratio = SAFE_HP_RATIO_ABS; + } + if (safe_ratio < CRITICAL_HP_RATIO_ABS) { + safe_ratio = CRITICAL_HP_RATIO_ABS; + } + + if (!(*current_member->MemberOfHealRotation())->SetArmorTypeSafeHPRatio(armor_type_value, safe_ratio)) { + c->Message( + Chat::White, + "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( + Chat::White, + "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_command_heal_rotation_casting_override(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail( + c, + "bot_command_heal_rotation_casting_override", + sep->arg[0], + "healrotationcastingoverride" + )) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "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( + Chat::White, + "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(Chat::White, "%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( + Chat::White, + "Casting override is 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( + Chat::White, + "Casting override is now '%s' for %s's Heal Rotation", + (((*current_member->MemberOfHealRotation())->CastingOverride()) ? ("on") : ("off")), + current_member->GetCleanName()); +} + +void bot_command_heal_rotation_change_interval(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail( + c, + "bot_command_heal_rotation_change_interval", + sep->arg[0], + "healrotationchangeinterval" + )) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message( + Chat::White, + "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( + Chat::White, + "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(Chat::White, "%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 = Strings::ToInt(change_interval_arg); + } else { + hr_change_interval_s = (*current_member->MemberOfHealRotation())->IntervalS(); + c->Message( + Chat::White, + "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( + Chat::White, + "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_command_heal_rotation_clear_hot(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_heal_rotation_clear_hot", sep->arg[0], "healrotationclearhot")) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "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( + Chat::White, + "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(Chat::White, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + if (!(*current_member->MemberOfHealRotation())->ClearHOTTarget()) { + c->Message(Chat::White, "Failed to clear %s's Heal Rotation HOT", current_member->GetCleanName()); + } + + c->Message(Chat::White, "Succeeded in clearing %s's Heal Rotation HOT", current_member->GetCleanName()); +} + +void bot_command_heal_rotation_clear_targets(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail( + c, + "bot_command_heal_rotation_clear_targets", + sep->arg[0], + "healrotationcleartargets" + )) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "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( + Chat::White, + "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(Chat::White, "%s is not a member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + if (!(*current_member->MemberOfHealRotation())->ClearTargetPool()) { + c->Message(Chat::White, "Failed to clear all targets from %s's Heal Rotation", current_member->GetCleanName()); + return; + } + + c->Message(Chat::White, "All targets have been cleared from %s's Heal Rotation", current_member->GetCleanName()); +} + +void bot_command_heal_rotation_create(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_heal_rotation_create", sep->arg[0], "healrotationcreate")) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message( + Chat::White, + "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(Chat::White, "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(Chat::White, "Error: Creator bot dereferenced to nullptr"); + return; + } + + if (creator_member->IsHealRotationMember()) { + c->Message(Chat::White, "%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 = Strings::ToInt(interval_arg); + } 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 = Strings::ToInt(interval_arg); + } + + 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( + Chat::White, + "Failed to add %s as a current member to a new Heal Rotation", + creator_member->GetCleanName()); + return; + } + + std::list member_list; + std::list target_list; + bool load_flag = false; + bool member_fail = false; + bool target_fail = false; + + database.botdb.LoadHealRotation(creator_member, member_list, target_list, load_flag, member_fail, target_fail); + + if (!load_flag) { + c->Message( + Chat::White, + "Successfully added %s as a current member to a new Heal Rotation", + creator_member->GetCleanName()); + return; + } + + if (!member_fail) { + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + for (auto member_iter: member_list) { + if (!member_iter || member_iter == creator_member->GetBotID()) { + continue; + } + + bool member_found = false; + for (auto bot_iter: sbl) { + if (bot_iter->GetBotID() != member_iter) { + continue; + } + + if (!bot_iter->JoinHealRotationMemberPool(creator_member->MemberOfHealRotation())) { + c->Message(Chat::White, "Failed to add member '%s'", bot_iter->GetCleanName()); + } + member_found = true; + + break; + } + + if (!member_found) { + c->Message(Chat::White, "Could not locate member with bot id '%u'", member_iter); + } + } + } + + if (!target_fail) { + for (auto target_iter: target_list) { + if (target_iter.empty()) { + continue; + } + + auto target_mob = entity_list.GetMob(target_iter.c_str()); + if (!target_mob) { + c->Message(Chat::White, "Could not locate target '%s'", target_iter.c_str()); + continue; + } + + if (!target_mob->JoinHealRotationTargetPool(creator_member->MemberOfHealRotation())) { + c->Message(Chat::White, "Failed to add target '%s'", target_mob->GetCleanName()); + } + } + } + + c->Message(Chat::White, "Successfully loaded %s's Heal Rotation", creator_member->GetCleanName()); +} + +void bot_command_heal_rotation_delete(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_heal_rotation_delete", sep->arg[0], "healrotationdelete")) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: () %s ([option: all]) ([member_name])", sep->arg[0]); + return; + } + + bool all_flag = false; + int name_arg = 1; + if (!strcasecmp(sep->arg[1], "all")) { + all_flag = true; + name_arg = 2; + } + + if (all_flag) { + database.botdb.DeleteAllHealRotations(c->CharacterID()); + return; + } + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[name_arg]); + if (sbl.empty()) { + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + } + if (sbl.empty()) { + c->Message( + Chat::White, + "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!database.botdb.DeleteHealRotation(current_member->GetBotID())) { + return; + } + + c->Message(Chat::White, "Succeeded in deleting %s's heal rotation", current_member->GetCleanName()); +} + +void bot_command_heal_rotation_fast_heals(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_heal_rotation_fast_heals", sep->arg[0], "healrotationfastheals")) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "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( + Chat::White, + "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(Chat::White, "%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( + Chat::White, + "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( + Chat::White, + "Fast heals are now '%s' for %s's Heal Rotation", + (((*current_member->MemberOfHealRotation())->FastHeals()) ? ("on") : ("off")), + current_member->GetCleanName()); +} + +void bot_command_heal_rotation_list(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_heal_rotation_list", sep->arg[0], "healrotationlist")) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "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( + Chat::White, + "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(Chat::White, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + c->Message(Chat::White, "Heal Rotation Settings:"); + + c->Message( + Chat::White, + "Current state: %s", + (((*current_member->MemberOfHealRotation())->IsActive()) ? ("active") : ("inactive"))); + c->Message(Chat::White, "Casting interval: %i seconds", (*current_member->MemberOfHealRotation())->IntervalS()); + c->Message( + Chat::White, + "Fast heals: '%s'", + (((*current_member->MemberOfHealRotation())->FastHeals()) ? ("on") : ("off"))); + c->Message( + Chat::White, + "Adaptive targeting: '%s'", + (((*current_member->MemberOfHealRotation())->AdaptiveTargeting()) ? ("on") : ("off"))); + c->Message( + Chat::White, + "Casting override: '%s'", + (((*current_member->MemberOfHealRotation())->CastingOverride()) ? ("on") : ("off"))); + c->Message( + Chat::White, + "HOT state: %s", + (((*current_member->MemberOfHealRotation())->IsHOTActive()) ? ("active") : ("inactive"))); + c->Message( + Chat::White, + "HOT target: %s", + (((*current_member->MemberOfHealRotation())->HOTTarget()) + ? ((*current_member->MemberOfHealRotation())->HOTTarget()->GetCleanName()) : ("null"))); + + c->Message( + Chat::White, "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( + Chat::White, "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( + Chat::White, "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( + Chat::White, "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( + Chat::White, "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(Chat::White, "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(Chat::White, "(%i) %s", (++member_index), member_iter->GetCleanName()); + } + if (!member_index) { + c->Message(Chat::White, "(0) None"); + } + + c->Message(Chat::White, "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(Chat::White, "(%i) %s", (++target_index), target_iter->GetCleanName()); + } + if (!target_index) { + c->Message(Chat::White, "(0) None"); + } +} + +void bot_command_heal_rotation_remove_member(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail( + c, + "bot_command_heal_rotation_remove_member", + sep->arg[0], + "healrotationremovemember" + )) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "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( + Chat::White, + "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(Chat::White, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + if (!current_member->LeaveHealRotationMemberPool()) { + c->Message(Chat::White, "Failed to remove %s from their Heal Rotation", current_member->GetCleanName()); + return; + } + + c->Message(Chat::White, "%s has been removed from their Heal Rotation", current_member->GetCleanName()); +} + +void bot_command_heal_rotation_remove_target(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail( + c, + "bot_command_heal_rotation_remove_target", + sep->arg[0], + "healrotationremovetarget" + )) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "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( + Chat::White, + "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(Chat::White, "%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(Chat::White, "No target exists by the name '%s'", sep->arg[1]); + return; + } + + if (!current_member->MemberOfHealRotation()->get()->IsTargetInPool(heal_target) || + !heal_target->LeaveHealRotationTargetPool()) { + c->Message(Chat::White, "Failed to remove heal target with a name of '%s'", heal_target->GetCleanName()); + return; + } + + c->Message( + Chat::White, + "Successfully removed heal target %s from %s's Heal Rotation", + heal_target->GetCleanName(), + current_member->GetCleanName()); +} + +void bot_command_heal_rotation_reset_limits(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail( + c, + "bot_command_heal_rotation_reset_limits", + sep->arg[0], + "healrotationresetlimits" + )) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "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( + Chat::White, + "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(Chat::White, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + (*current_member->MemberOfHealRotation())->ResetArmorTypeHPLimits(); + + c->Message( + Chat::White, + "Class Armor Type HP limit criteria has been set to default values for %s's Heal Rotation", + current_member->GetCleanName()); +} + +void bot_command_heal_rotation_save(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_heal_rotation_save", sep->arg[0], "healrotationsave")) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "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( + Chat::White, + "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(Chat::White, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + bool member_fail = false; + bool target_fail = false; + if (!database.botdb.SaveHealRotation(current_member, member_fail, target_fail)) { + return; + } + if (member_fail) { + c->Message(Chat::White, "Failed to save heal rotation members"); + } + if (target_fail) { + c->Message(Chat::White, "Failed to save heal rotation targets"); + } + + c->Message(Chat::White, "Succeeded in saving %s's heal rotation", current_member->GetCleanName()); +} + +void bot_command_heal_rotation_set_hot(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_heal_rotation_set_hot", sep->arg[0], "healrotationsethot")) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: () %s [heal_override_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( + Chat::White, + "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(Chat::White, "%s is not a member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + auto hot_target = entity_list.GetMob(sep->arg[1]); + if (!hot_target) { + c->Message(Chat::White, "No target exists by the name '%s'", sep->arg[1]); + return; + } + + if (!(*current_member->MemberOfHealRotation())->IsTargetInPool(hot_target)) { + c->Message( + Chat::White, + "%s is not a target in %s's Heal Rotation", + hot_target->GetCleanName(), + current_member->GetCleanName()); + return; + } + + if (!(*current_member->MemberOfHealRotation())->SetHOTTarget(hot_target)) { + c->Message( + Chat::White, + "Failed to set %s as the HOT in %s's Heal Rotation", + hot_target->GetCleanName(), + current_member->GetCleanName()); + return; + } + + c->Message( + Chat::White, + "Succeeded in setting %s as the HOT in %s's Heal Rotation", + hot_target->GetCleanName(), + current_member->GetCleanName()); +} + +void bot_command_heal_rotation_start(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_heal_rotation_start", sep->arg[0], "healrotationstart")) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "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( + Chat::White, + "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(Chat::White, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + if ((*current_member->MemberOfHealRotation())->IsActive()) { + c->Message(Chat::White, "%s's Heal Rotation is already active", current_member->GetCleanName()); + return; + } + + if (!current_member->MemberOfHealRotation()->get()->Start()) { + c->Message(Chat::White, "Failed to start %s's Heal Rotation", current_member->GetCleanName()); + return; + } + + c->Message(Chat::White, "%s's Heal Rotation is now active", current_member->GetCleanName()); +} + +void bot_command_heal_rotation_stop(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_heal_rotation_stop", sep->arg[0], "healrotationstop")) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "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( + Chat::White, + "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(Chat::White, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(Chat::White, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + if (!(*current_member->MemberOfHealRotation())->IsActive()) { + c->Message(Chat::White, "%s's Heal Rotation is already inactive", current_member->GetCleanName()); + return; + } + + if (!current_member->MemberOfHealRotation()->get()->Stop()) { + c->Message(Chat::White, "Failed to stop %s's Heal Rotation", current_member->GetCleanName()); + return; + } + + c->Message(Chat::White, "%s's Heal Rotation is now inactive", current_member->GetCleanName()); +} diff --git a/zone/bot_commands/help.cpp b/zone/bot_commands/help.cpp new file mode 100644 index 000000000..d2b143ee7 --- /dev/null +++ b/zone/bot_commands/help.cpp @@ -0,0 +1,36 @@ +#include "../bot_command.h" + +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(Chat::White, "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( + Chat::White, + fmt::format( + "^{} - {}", + command_iter.first, + command_iter.second->desc ? command_iter.second->desc : "No Description" + ).c_str() + ); + + ++bot_commands_shown; + } + if (parse->PlayerHasQuestSub(EVENT_BOT_COMMAND)) { + int i = parse->EventPlayer(EVENT_BOT_COMMAND, c, sep->msg, 0); + if (i >= 1) { + bot_commands_shown += i; + } + } + c->Message(Chat::White, "%d bot command%s listed.", bot_commands_shown, bot_commands_shown != 1 ? "s" : ""); + c->Message(Chat::White, "type %ccommand [help | usage] for more information", BOT_COMMAND_CHAR); +} diff --git a/zone/bot_commands/hold.cpp b/zone/bot_commands/hold.cpp new file mode 100644 index 000000000..49f0f516f --- /dev/null +++ b/zone/bot_commands/hold.cpp @@ -0,0 +1,67 @@ +#include "../bot_command.h" + +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(Chat::White, "usage: %s ([option: clear]) [actionable: byname | ownergroup | ownerraid | namesgroup | healrotation | byclass | byrace | spawned]] ([actionable_name])", sep->arg[0]); + return; + } + const int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_Type2); + + bool clear = false; + int ab_arg = 1; + int name_arg = 2; + + std::string clear_arg = sep->arg[1]; + if (!clear_arg.compare("clear")) { + + clear = true; + ab_arg = 2; + name_arg = 3; + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[name_arg] : nullptr, class_race_check ? atoi(sep->arg[name_arg]) : 0) == ActionableBots::ABT_None) { + return; + } + + sbl.remove(nullptr); + for (auto bot_iter : sbl) { + + if (clear) { + bot_iter->SetHoldFlag(false); + } + else { + bot_iter->SetHoldMode(); + } + } + + if (sbl.size() == 1) { + Bot::BotGroupSay( + sbl.front(), + fmt::format( + "{}olding my attacks.", + clear ? "No longer h" : "H" + ).c_str() + ); + } else { + c->Message( + Chat::White, + fmt::format( + "{} of your bots are {}holding their attacks.", + sbl.size(), + clear ? "no longer " : "" + ).c_str() + ); + } +} diff --git a/zone/bot_commands/identify.cpp b/zone/bot_commands/identify.cpp new file mode 100644 index 000000000..7ac90a4e4 --- /dev/null +++ b/zone/bot_commands/identify.cpp @@ -0,0 +1,38 @@ +#include "../bot_command.h" + +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(Chat::White, "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); +} diff --git a/zone/bot_commands/inspect_message.cpp b/zone/bot_commands/inspect_message.cpp new file mode 100644 index 000000000..dc2b7d868 --- /dev/null +++ b/zone/bot_commands/inspect_message.cpp @@ -0,0 +1 @@ +#include "../bot_command.h" diff --git a/zone/bot_commands/inventory.cpp b/zone/bot_commands/inventory.cpp new file mode 100644 index 000000000..b934f96a2 --- /dev/null +++ b/zone/bot_commands/inventory.cpp @@ -0,0 +1,338 @@ +#include "../bot_command.h" + +void bot_command_inventory(Client *c, const Seperator *sep) +{ + + std::list subcommand_list; + subcommand_list.push_back("inventorygive"); + subcommand_list.push_back("inventorylist"); + subcommand_list.push_back("inventoryremove"); + subcommand_list.push_back("inventorywindow"); + + 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_inventory_give(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_inventory_give", sep->arg[0], "inventorygive")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} ([actionable: target | byname] ([actionable_name]))", + sep->arg[0] + ).c_str() + ); + 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(Chat::White, "ActionableBots returned 'nullptr'"); + return; + } + + my_bot->FinishTrade(c, Bot::BotTradeClientNoDropNoTrade); +} + +void bot_command_inventory_list(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_inventory_list", sep->arg[0], "inventorylist")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} ([actionable: target | byname] ([actionable_name]))", + sep->arg[0] + ).c_str() + ); + 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(Chat::White, "ActionableBots returned 'nullptr'"); + return; + } + + const EQ::ItemInstance* inst = nullptr; + const EQ::ItemData * item = nullptr; + bool is_2h_weapon = false; + + EQ::SayLinkEngine linker; + linker.SetLinkType(EQ::saylink::SayLinkItemInst); + + uint32 inventory_count = 0; + for (uint16 slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; ++slot_id) { + if (slot_id == EQ::invslot::slotSecondary && is_2h_weapon) { + continue; + } + + inst = my_bot->CastToBot()->GetBotItem(slot_id); + if (!inst || !inst->GetItem()) { + c->Message( + Chat::White, + fmt::format( + "Slot {} ({}) | Empty", + slot_id, + EQ::invslot::GetInvPossessionsSlotName(slot_id) + ).c_str() + ); + continue; + } + + item = inst->GetItem(); + if (slot_id == EQ::invslot::slotPrimary && item->IsType2HWeapon()) { + is_2h_weapon = true; + } + + linker.SetItemInst(inst); + c->Message( + Chat::White, + fmt::format( + "Slot {} ({}) | {} | {}", + slot_id, + EQ::invslot::GetInvPossessionsSlotName(slot_id), + linker.GenerateLink(), + Saylink::Silent( + fmt::format("^inventoryremove {}", slot_id), + "Remove" + ) + ).c_str() + ); + + ++inventory_count; + } + + uint32 database_count = 0; + database.botdb.QueryInventoryCount(my_bot->GetBotID(), database_count); + + if (inventory_count != database_count) { + c->Message( + Chat::White, + fmt::format( + "Inventory-database item count mismatch, inventory has {} item{} and the database has {} item{}.", + inventory_count, + inventory_count != 1 ? "s" : "", + database_count, + database_count != 1 ? "s" : "" + ).c_str() + ); + } +} + +void bot_command_inventory_remove(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_inventory_remove", sep->arg[0], "inventoryremove")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Slot ID: 0-22] ([actionable: target | byname] ([actionable_name]))", + sep->arg[0] + ).c_str() + ); + return; + } + + int ab_mask = (ActionableBots::ABM_Target | ActionableBots::ABM_ByName); + + if (c->GetTradeskillObject() || (c->trade->state == Trading)) { + c->MessageString(Chat::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(Chat::White, "ActionableBots returned 'nullptr'"); + return; + } + + if (!sep->IsNumber(1)) { + c->Message(Chat::White, "Slot ID must be a number."); + return; + } + + auto slot_id = static_cast(Strings::ToUnsignedInt(sep->arg[1])); + if (slot_id > EQ::invslot::EQUIPMENT_END || slot_id < EQ::invslot::EQUIPMENT_BEGIN) { + c->Message(Chat::White, "Valid slots are 0 to 22."); + return; + } + + auto* inst = my_bot->GetBotItem(slot_id); + if (!inst) { + std::string slot_message = "is"; + switch (slot_id) { + case EQ::invslot::slotShoulders: + case EQ::invslot::slotArms: + case EQ::invslot::slotHands: + case EQ::invslot::slotLegs: + case EQ::invslot::slotFeet: + slot_message = "are"; + break; + default: + break; + } + + my_bot->OwnerMessage( + fmt::format( + "My {} (Slot {}) {} already unequipped.", + EQ::invslot::GetInvPossessionsSlotName(slot_id), + slot_id, + slot_message + ) + ); + return; + } + + const auto* itm = inst->GetItem(); + + if (inst && itm && c->CheckLoreConflict(itm)) { + c->MessageString(Chat::White, PICK_LORE); + return; + } + + for (int m = EQ::invaug::SOCKET_BEGIN; m <= EQ::invaug::SOCKET_END; ++m) { + EQ::ItemInstance* augment = inst->GetAugment(m); + if (!augment) { + continue; + } + + if (!c->CheckLoreConflict(augment->GetItem())) { + continue; + } + + c->MessageString(Chat::White, PICK_LORE); + return; + } + + if (itm) { + EQ::SayLinkEngine linker; + linker.SetLinkType(EQ::saylink::SayLinkItemInst); + linker.SetItemInst(inst); + + c->PushItemOnCursor(*inst, true); + if ( + slot_id == EQ::invslot::slotRange || + slot_id == EQ::invslot::slotAmmo + ) { + my_bot->SetBotArcherySetting(false, true); + } + + my_bot->RemoveBotItemBySlot(slot_id); + my_bot->BotRemoveEquipItem(slot_id); + my_bot->CalcBotStats(c->GetBotOption(Client::booStatsUpdate)); + + my_bot->OwnerMessage( + fmt::format( + "I have unequipped {} from my {} (Slot {}).", + linker.GenerateLink(), + EQ::invslot::GetInvPossessionsSlotName(slot_id), + slot_id + ) + ); + + if (parse->BotHasQuestSub(EVENT_UNEQUIP_ITEM_BOT)) { + const auto& export_string = fmt::format( + "{} {}", + inst->IsStackable() ? inst->GetCharges() : 1, + slot_id + ); + + std::vector args = { inst }; + + parse->EventBot(EVENT_UNEQUIP_ITEM_BOT, my_bot, nullptr, export_string, inst->GetID(), &args); + } + } +} + +void bot_command_inventory_window(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_inventory_window", sep->arg[0], "inventorywindow")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [actionable: target]", + sep->arg[0] + ).c_str() + ); + return; + } + + int ab_mask = ActionableBots::ABM_Target; + + 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(Chat::White, "ActionableBots returned 'nullptr'"); + return; + } + + std::string window_title = fmt::format( + "{}'s Inventory", + my_bot->GetCleanName() + ); + + std::string window_text = ""; + for (uint16 slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; ++slot_id) { + const EQ::ItemData * item = nullptr; + const EQ::ItemInstance* inst = my_bot->CastToBot()->GetBotItem(slot_id); + if (inst) { + item = inst->GetItem(); + } + + window_text.append( + fmt::format( + "", + EQ::invslot::GetInvPossessionsSlotName(slot_id), + item ? "" : "", + item ? item->Name : "Empty" + ) + ); + } + window_text.append("
{}{}{}
"); + + c->SendPopupToClient( + window_title.c_str(), + window_text.c_str() + ); +} diff --git a/zone/bot_commands/invisibility.cpp b/zone/bot_commands/invisibility.cpp new file mode 100644 index 000000000..dd826ebc8 --- /dev/null +++ b/zone/bot_commands/invisibility.cpp @@ -0,0 +1,57 @@ +#include "../bot_command.h" + +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(Chat::White, "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(Chat::White, "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); +} diff --git a/zone/bot_commands/item_use.cpp b/zone/bot_commands/item_use.cpp new file mode 100644 index 000000000..1640ff0e3 --- /dev/null +++ b/zone/bot_commands/item_use.cpp @@ -0,0 +1,211 @@ +#include "../bot_command.h" + +void bot_command_item_use(Client* c, const Seperator* sep) +{ + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: [%s empty] will display only bots that can use the item in an empty slot.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s byclass classID] - Example: [%s byclass 7] will display only bots that match the class that can use the item. Example is a Monk, use [^create help] for a list of class IDs.", sep->arg[0], sep->arg[0]); + c->Message(Chat::White, "usage: [%s casteronly] will display only caster bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s hybridonly] will display only hybrid bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s meleeonly] will display only melee bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s wiscasteronly] will display only Wisdom-based Caster bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s intcasteronly] will display only Intelligence-based Caster bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s plateonly] will display only Plate-wearing bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s chainonly] will display only Chain-wearing bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s leatheronly] will display only Leather-wearing bots that can use the item.", sep->arg[0]); + c->Message(Chat::White, "usage: [%s clothonly] will display only Cloth-wearing bots that can use the item.", sep->arg[0]); + return; + } + + bool empty_only = false; + int8 class_mask = 0; + bool caster_only = false; + bool hybrid_only = false; + bool melee_only = false; + bool wis_caster_only = false; + bool int_caster_only = false; + bool plate_only = false; + bool chain_only = false; + bool leather_only = false; + bool cloth_only = false; + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + if (arg1.compare("empty") == 0) { + empty_only = true; + } + else if (arg1.compare("byclass") == 0) { + if (Strings::IsNumber(sep->arg[2])) { + class_mask = Strings::ToUnsignedInt(sep->arg[2]); + if (!(class_mask >= Class::Warrior && class_mask <= Class::Berserker)) { + c->Message(Chat::White, "Invalid class range, you must choose between 1 (Warrior) and 15 (Beastlord)"); + return; + } + } + } + else if (arg1.compare("casteronly") == 0) { + caster_only = true; + } + else if (arg1.compare("hybridonly") == 0) { + hybrid_only = true; + } + else if (arg1.compare("meleeonly") == 0) { + melee_only = true; + } + else if (arg1.compare("wiscasteronly") == 0) { + wis_caster_only = true; + } + else if (arg1.compare("intcasteronly") == 0) { + int_caster_only = true; + } + else if (arg1.compare("plateonly") == 0) { + plate_only = true; + } + else if (arg1.compare("chainonly") == 0) { + chain_only = true; + } + else if (arg1.compare("leatheronly") == 0) { + leather_only = true; + } + else if (arg1.compare("clothonly") == 0) { + cloth_only = true; + } + else if (!arg1.empty()) { + c->Message(Chat::White, "Please choose the correct subtype. For help use %s help.", sep->arg[0]); + return; + } + const auto item_instance = c->GetInv().GetItem(EQ::invslot::slotCursor); + if (!item_instance) { + c->Message(Chat::White, "No item found on cursor!"); + return; + } + + auto item_data = item_instance->GetItem(); + if (!item_data) { + c->Message(Chat::White, "No data found for cursor item!"); + return; + } + + if (item_data->ItemClass != EQ::item::ItemClassCommon || item_data->Slots == 0) { + c->Message(Chat::White, "'%s' is not an equipable item!", item_data->Name); + return; + } + + std::vector equipable_slot_list; + for (int16 equipable_slot = EQ::invslot::EQUIPMENT_BEGIN; equipable_slot <= EQ::invslot::EQUIPMENT_END; ++equipable_slot) { + if (item_data->Slots & (1 << equipable_slot)) { + equipable_slot_list.emplace_back(equipable_slot); + } + } + + EQ::SayLinkEngine linker; + linker.SetLinkType(EQ::saylink::SayLinkItemInst); + + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + if (class_mask) { + ActionableBots::Filter_ByClasses(c, sbl, GetPlayerClassBit(class_mask)); + } + + for (const auto& bot_iter : sbl) { + if (!bot_iter) { + continue; + } + if (caster_only && !IsCasterClass(bot_iter->GetClass())) { + continue; + } + if (hybrid_only && !IsSpellFighterClass(bot_iter->GetClass())) { + continue; + } + if (melee_only && !IsNonSpellFighterClass(bot_iter->GetClass())) { + continue; + } + if (wis_caster_only && !IsWISCasterClass(bot_iter->GetClass())) { + continue; + } + if (int_caster_only && !IsINTCasterClass(bot_iter->GetClass())) { + continue; + } + if (plate_only && !IsPlateClass(bot_iter->GetClass())) { + continue; + } + if (chain_only && !IsChainClass(bot_iter->GetClass())) { + continue; + } + if (leather_only && !IsLeatherClass(bot_iter->GetClass())) { + continue; + } + if (cloth_only && !IsClothClass(bot_iter->GetClass())) { + continue; + } + if (((~item_data->Races) & GetPlayerRaceBit(bot_iter->GetRace())) || ((~item_data->Classes) & GetPlayerClassBit(bot_iter->GetClass()))) { + continue; + } + + for (const auto& slot_iter : equipable_slot_list) { + // needs more failure criteria - this should cover the bulk for now + if (slot_iter == EQ::invslot::slotSecondary && item_data->Damage && !bot_iter->CanThisClassDualWield()) { + continue; + } + + auto equipped_item = bot_iter->GetInv()[slot_iter]; + + if (equipped_item && !empty_only) { + linker.SetItemInst(equipped_item); + + c->Message( + Chat::Say, + fmt::format( + "{} says, 'I can use that for my {} instead of my {}! Would you like to {} my {}?'", + Saylink::Silent( + fmt::format( + "^inventorygive byname {}", + bot_iter->GetCleanName() + ), + bot_iter->GetCleanName() + ), + EQ::invslot::GetInvPossessionsSlotName(slot_iter), + linker.GenerateLink(), + Saylink::Silent( + fmt::format( + "^inventoryremove {} byname {}", + slot_iter, + bot_iter->GetCleanName() + ), + "remove" + ), + linker.GenerateLink() + ).c_str() + ); + + bot_iter->DoAnim(29); + } + else if (!equipped_item) { + c->Message( + Chat::Say, + fmt::format( + "{} says, 'I can use that for my {}! Would you like to {} it to me?'", + Saylink::Silent( + fmt::format( + "^inventorygive byname {}", + bot_iter->GetCleanName() + ), + bot_iter->GetCleanName() + ), + EQ::invslot::GetInvPossessionsSlotName(slot_iter), + Saylink::Silent( + fmt::format( + "^inventorygive byname {}", + bot_iter->GetCleanName() + ), + "give" + ) + ).c_str() + ); + + bot_iter->DoAnim(29); + } + } + } +} diff --git a/zone/bot_commands/levitation.cpp b/zone/bot_commands/levitation.cpp new file mode 100644 index 000000000..d4b5cee28 --- /dev/null +++ b/zone/bot_commands/levitation.cpp @@ -0,0 +1,38 @@ +#include "../bot_command.h" + +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(Chat::White, "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); +} diff --git a/zone/bot_commands/list.cpp b/zone/bot_commands/list.cpp new file mode 100644 index 000000000..dc2b7d868 --- /dev/null +++ b/zone/bot_commands/list.cpp @@ -0,0 +1 @@ +#include "../bot_command.h" diff --git a/zone/bot_commands/lull.cpp b/zone/bot_commands/lull.cpp new file mode 100644 index 000000000..d39f534d1 --- /dev/null +++ b/zone/bot_commands/lull.cpp @@ -0,0 +1,43 @@ +#include "../bot_command.h" + +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(Chat::White, "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); +} diff --git a/zone/bot_commands/mesmerize.cpp b/zone/bot_commands/mesmerize.cpp new file mode 100644 index 000000000..532a3c404 --- /dev/null +++ b/zone/bot_commands/mesmerize.cpp @@ -0,0 +1,43 @@ +#include "../bot_command.h" + +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(Chat::White, "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_value[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); +} diff --git a/zone/bot_commands/movement_speed.cpp b/zone/bot_commands/movement_speed.cpp new file mode 100644 index 000000000..d1b884154 --- /dev/null +++ b/zone/bot_commands/movement_speed.cpp @@ -0,0 +1,50 @@ +#include "../bot_command.h" + +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(Chat::White, "usage: () %s ([group | sow])", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_MovementSpeed); + return; + } + + bool group = false; + bool sow = false; + std::string arg1 = sep->arg[1]; + if (!arg1.compare("group")) + group = true; + else if (!arg1.compare("sow")) + sow = 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 (!sow && (local_entry->group != group)) + continue; + if (sow && (local_entry->spell_id != 278)) // '278' = single-target "Spirit of Wolf" + 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); +} diff --git a/zone/bot_commands/name.cpp b/zone/bot_commands/name.cpp new file mode 100644 index 000000000..688b15bbf --- /dev/null +++ b/zone/bot_commands/name.cpp @@ -0,0 +1,76 @@ +#include "../bot_command.h" + +void bot_command_surname(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == '\0' || sep->IsNumber(1)) { + c->Message(Chat::White, "You must specify a [surname] to use this command (use _ to define spaces or -remove to clear.)"); + return; + } + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must a bot that you own to use this command"); + return; + } + if (strlen(sep->arg[1]) > 31) { + c->Message(Chat::White, "Surname must be 31 characters or less."); + return; + } + std::string bot_surname = sep->arg[1]; + bot_surname = (bot_surname == "-remove") ? "" : bot_surname; + std::replace(bot_surname.begin(), bot_surname.end(), '_', ' '); + + my_bot->SetSurname(bot_surname); + if (database.botdb.SaveBot(my_bot)) { + c->Message(Chat::White, "Bot Surname Saved."); + } +} + +void bot_command_title(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == '\0' || sep->IsNumber(1)) { + c->Message(Chat::White, "You must specify a [title] to use this command. (use _ to define spaces or -remove to clear.)"); + return; + } + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must a bot that you own to use this command"); + return; + } + if (strlen(sep->arg[1]) > 31) { + c->Message(Chat::White, "Title must be 31 characters or less."); + return; + } + std::string bot_title = sep->arg[1]; + bot_title = (bot_title == "-remove") ? "" : bot_title; + std::replace(bot_title.begin(), bot_title.end(), '_', ' '); + + my_bot->SetTitle(bot_title); + if (database.botdb.SaveBot(my_bot)) { + c->Message(Chat::White, "Bot Title Saved."); + } +} + +void bot_command_suffix(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] == '\0' || sep->IsNumber(1)) { + c->Message(Chat::White, "You must specify a [suffix] to use this command. (use _ to define spaces or -remove to clear.)"); + return; + } + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must a bot that you own to use this command"); + return; + } + if (strlen(sep->arg[1]) > 31) { + c->Message(Chat::White, "Suffix must be 31 characters or less."); + return; + } + std::string bot_suffix = sep->arg[1]; + bot_suffix = (bot_suffix == "-remove") ? "" : bot_suffix; + std::replace(bot_suffix.begin(), bot_suffix.end(), '_', ' '); + + my_bot->SetSuffix(bot_suffix); + if (database.botdb.SaveBot(my_bot)) { + c->Message(Chat::White, "Bot Suffix Saved."); + } +} diff --git a/zone/bot_commands/out_of_combat.cpp b/zone/bot_commands/out_of_combat.cpp new file mode 100644 index 000000000..dc2b7d868 --- /dev/null +++ b/zone/bot_commands/out_of_combat.cpp @@ -0,0 +1 @@ +#include "../bot_command.h" diff --git a/zone/bot_commands/owner_option.cpp b/zone/bot_commands/owner_option.cpp new file mode 100644 index 000000000..b69c71fd9 --- /dev/null +++ b/zone/bot_commands/owner_option.cpp @@ -0,0 +1,315 @@ +#include "../bot_command.h" + +void bot_command_owner_option(Client *c, const Seperator *sep) +{ + if (helper_is_help_or_usage(sep->arg[1])) { + + c->Message(Chat::White, "usage: %s [option] [argument]", sep->arg[0]); + + std::string window_title = "Bot Owner Options"; + std::string window_text = + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "
Option
------
Argument
-------
Notes
-----
deathmarqueeenable | disablemarquee message on death
null(toggles)
statsupdateenable | disablereport stats on update
null(toggles)
spawnmessagesay | tell | silentspawn message into channel
class | defaultspawn with class-based message
altcombatenable | disableuse alternate ai combat behavior
null(toggles)
autodefendenable | disablebots defend owner when aggroed
null(toggles)
buffcounterenable | disablemarquee message on buff counter change
null(toggles)
monkwumessageenable | disabledisplays monk wu trigger messages
null(toggles)
currentshow current settings
"; + + c->SendPopupToClient(window_title.c_str(), window_text.c_str()); + + return; + } + + std::string owner_option(sep->arg[1]); + std::string argument(sep->arg[2]); + + if (!owner_option.compare("deathmarquee")) { + + if (!argument.compare("enable")) { + c->SetBotOption(Client::booDeathMarquee, true); + } + else if (!argument.compare("disable")) { + c->SetBotOption(Client::booDeathMarquee, false); + } + else { + c->SetBotOption(Client::booDeathMarquee, !c->GetBotOption(Client::booDeathMarquee)); + } + + database.botdb.SaveOwnerOption(c->CharacterID(), Client::booDeathMarquee, c->GetBotOption(Client::booDeathMarquee)); + + c->Message(Chat::White, "Bot 'death marquee' is now %s.", (c->GetBotOption(Client::booDeathMarquee) ? "enabled" : "disabled")); + } + else if (!owner_option.compare("statsupdate")) { + + if (!argument.compare("enable")) { + c->SetBotOption(Client::booStatsUpdate, true); + } + else if (!argument.compare("disable")) { + c->SetBotOption(Client::booStatsUpdate, false); + } + else { + c->SetBotOption(Client::booStatsUpdate, !c->GetBotOption(Client::booStatsUpdate)); + } + + database.botdb.SaveOwnerOption(c->CharacterID(), Client::booStatsUpdate, c->GetBotOption(Client::booStatsUpdate)); + + c->Message(Chat::White, "Bot 'stats update' is now %s.", (c->GetBotOption(Client::booStatsUpdate) ? "enabled" : "disabled")); + } + else if (!owner_option.compare("spawnmessage")) { + + Client::BotOwnerOption boo = Client::_booCount; + + if (!argument.compare("say")) { + + boo = Client::booSpawnMessageSay; + c->SetBotOption(Client::booSpawnMessageSay, true); + c->SetBotOption(Client::booSpawnMessageTell, false); + } + else if (!argument.compare("tell")) { + + boo = Client::booSpawnMessageSay; + c->SetBotOption(Client::booSpawnMessageSay, false); + c->SetBotOption(Client::booSpawnMessageTell, true); + } + else if (!argument.compare("silent")) { + + boo = Client::booSpawnMessageSay; + c->SetBotOption(Client::booSpawnMessageSay, false); + c->SetBotOption(Client::booSpawnMessageTell, false); + } + else if (!argument.compare("class")) { + + boo = Client::booSpawnMessageClassSpecific; + c->SetBotOption(Client::booSpawnMessageClassSpecific, true); + } + else if (!argument.compare("default")) { + + boo = Client::booSpawnMessageClassSpecific; + c->SetBotOption(Client::booSpawnMessageClassSpecific, false); + } + else { + + c->Message(Chat::White, "Owner option '%s' argument '%s' is not recognized.", owner_option.c_str(), argument.c_str()); + return; + } + + if (boo == Client::booSpawnMessageSay) { + + database.botdb.SaveOwnerOption( + c->CharacterID(), + std::pair( + Client::booSpawnMessageSay, + Client::booSpawnMessageTell + ), + std::pair( + c->GetBotOption(Client::booSpawnMessageSay), + c->GetBotOption(Client::booSpawnMessageTell) + ) + ); + } + else if (boo == Client::booSpawnMessageClassSpecific) { + + database.botdb.SaveOwnerOption( + c->CharacterID(), + Client::booSpawnMessageClassSpecific, + c->GetBotOption(Client::booSpawnMessageClassSpecific) + ); + } + else { + + c->Message(Chat::White, "Bot 'spawn message' is now ERROR."); + return; + } + + c->Message(Chat::White, "Bot 'spawn message' is now %s.", argument.c_str()); + } + else if (!owner_option.compare("altcombat")) { + + if (RuleB(Bots, AllowOwnerOptionAltCombat)) { + + if (!argument.compare("enable")) { + c->SetBotOption(Client::booAltCombat, true); + } + else if (!argument.compare("disable")) { + c->SetBotOption(Client::booAltCombat, false); + } + else { + c->SetBotOption(Client::booAltCombat, !c->GetBotOption(Client::booAltCombat)); + } + + database.botdb.SaveOwnerOption(c->CharacterID(), Client::booAltCombat, c->GetBotOption(Client::booAltCombat)); + + c->Message(Chat::White, "Bot 'alt combat' is now %s.", (c->GetBotOption(Client::booAltCombat) ? "enabled" : "disabled")); + } + else { + c->Message(Chat::White, "Bot owner option 'altcombat' is not allowed on this server."); + } + } + else if (!owner_option.compare("autodefend")) { + + if (RuleB(Bots, AllowOwnerOptionAutoDefend)) { + + if (!argument.compare("enable")) { + c->SetBotOption(Client::booAutoDefend, true); + } + else if (!argument.compare("disable")) { + c->SetBotOption(Client::booAutoDefend, false); + } + else { + c->SetBotOption(Client::booAutoDefend, !c->GetBotOption(Client::booAutoDefend)); + } + + database.botdb.SaveOwnerOption(c->CharacterID(), Client::booAutoDefend, c->GetBotOption(Client::booAutoDefend)); + + c->Message(Chat::White, "Bot 'auto defend' is now %s.", (c->GetBotOption(Client::booAutoDefend) ? "enabled" : "disabled")); + } + else { + c->Message(Chat::White, "Bot owner option 'autodefend' is not allowed on this server."); + } + } + else if (!owner_option.compare("buffcounter")) { + + if (!argument.compare("enable")) { + c->SetBotOption(Client::booBuffCounter, true); + } + else if (!argument.compare("disable")) { + c->SetBotOption(Client::booBuffCounter, false); + } + else { + c->SetBotOption(Client::booBuffCounter, !c->GetBotOption(Client::booBuffCounter)); + } + + database.botdb.SaveOwnerOption(c->CharacterID(), Client::booBuffCounter, c->GetBotOption(Client::booBuffCounter)); + + c->Message(Chat::White, "Bot 'buff counter' is now %s.", (c->GetBotOption(Client::booBuffCounter) ? "enabled" : "disabled")); + } + else if (!owner_option.compare("monkwumessage")) { + + if (!argument.compare("enable")) { + c->SetBotOption(Client::booMonkWuMessage, true); + } + else if (!argument.compare("disable")) { + c->SetBotOption(Client::booMonkWuMessage, false); + } + else { + c->SetBotOption(Client::booMonkWuMessage, !c->GetBotOption(Client::booMonkWuMessage)); + } + + database.botdb.SaveOwnerOption(c->CharacterID(), Client::booMonkWuMessage, c->GetBotOption(Client::booMonkWuMessage)); + + c->Message( + Chat::White, + "Bot 'monk wu message' is now %s.", + (c->GetBotOption(Client::booMonkWuMessage) ? "enabled" : "disabled") + ); + } + else if (!owner_option.compare("current")) { + + std::string window_title = "Current Bot Owner Options Settings"; + std::string window_text = fmt::format( + "" + "" + "" + "" + "" + "" "" "" "" + "" "" "" "" + "" "" "" "" + "" "" "" "" + "" "" "" "" + "" "" "" "" + "" "" "" "" + "" "" "" "" + "
Option
------
Argument
-------
deathmarquee{}
statsupdate{}
spawnmessage{}
spawnmessage{}
altcombat{}
autodefend{}
buffcounter{}
monkwumessage{}
", + (c->GetBotOption(Client::booDeathMarquee) ? "enabled" : "disabled"), + (c->GetBotOption(Client::booStatsUpdate) ? "enabled" : "disabled"), + (c->GetBotOption(Client::booSpawnMessageSay) ? "say" : (c->GetBotOption(Client::booSpawnMessageTell) ? "tell" : "silent")), + (c->GetBotOption(Client::booSpawnMessageClassSpecific) ? "class" : "default"), + (RuleB(Bots, AllowOwnerOptionAltCombat) ? (c->GetBotOption(Client::booAltCombat) ? "enabled" : "disabled") : "restricted"), + (RuleB(Bots, AllowOwnerOptionAutoDefend) ? (c->GetBotOption(Client::booAutoDefend) ? "enabled" : "disabled") : "restricted"), + (c->GetBotOption(Client::booBuffCounter) ? "enabled" : "disabled"), + (c->GetBotOption(Client::booMonkWuMessage) ? "enabled" : "disabled") + ); + + c->SendPopupToClient(window_title.c_str(), window_text.c_str()); + } + else { + c->Message(Chat::White, "Owner option '%s' is not recognized.", owner_option.c_str()); + } +} diff --git a/zone/bot_commands/pet.cpp b/zone/bot_commands/pet.cpp new file mode 100644 index 000000000..f6c6ae21d --- /dev/null +++ b/zone/bot_commands/pet.cpp @@ -0,0 +1,168 @@ +#include "../bot_command.h" + +void bot_command_pet(Client *c, const Seperator *sep) +{ + + std::list subcommand_list; + subcommand_list.push_back("petgetlost"); + subcommand_list.push_back("petremove"); + subcommand_list.push_back("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_pet_get_lost(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_pet_get_lost", sep->arg[0], "petgetlost")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s ([actionable: target | byname | ownergroup | targetgroup | namesgroup | healrotation | spawned] ([actionable_name]))", sep->arg[0]); + return; + } + 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; + + int summoned_pet = 0; + for (auto bot_iter : sbl) { + if (!bot_iter->GetPet() || bot_iter->GetPet()->IsCharmed()) + continue; + + bot_iter->GetPet()->SayString(PET_GETLOST_STRING); + bot_iter->GetPet()->Depop(false); + bot_iter->SetPetID(0); + database.botdb.DeletePetItems(bot_iter->GetBotID()); + database.botdb.DeletePetBuffs(bot_iter->GetBotID()); + database.botdb.DeletePetStats(bot_iter->GetBotID()); + ++summoned_pet; + } + + c->Message(Chat::White, "%i of your bots released their summoned pet%s", summoned_pet, (summoned_pet == 1) ? "" : "s"); +} + +void bot_command_pet_remove(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_pet_remove", sep->arg[0], "petremove")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "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_bitmasks[Class::Druid] | player_class_bitmasks[Class::Necromancer] | player_class_bitmasks[Class::Enchanter]); + ActionableBots::Filter_ByClasses(c, sbl, class_mask); + if (sbl.empty()) { + c->Message(Chat::White, "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()->SayString(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(Chat::White, "%i of your bots set for charming, %i of your bots set for summoned pet use", charmed_pet, summoned_pet); +} + +void bot_command_pet_set_type(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_pet_set_type", sep->arg[0], "petsettype")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [type: water | fire | air | earth | monster] ([actionable: target | byname] ([actionable_name]))", sep->arg[0]); + c->Message(Chat::White, "requires one of the following bot classes:"); + c->Message(Chat::White, "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(Chat::White, "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_bitmasks[Class::Magician]; + ActionableBots::Filter_ByClasses(c, sbl, class_mask); + if (sbl.empty()) { + c->Message(Chat::White, "You have no spawned Magician bots"); + return; + } + + ActionableBots::Filter_ByMinLevel(c, sbl, level_req); + if (sbl.empty()) { + c->Message(Chat::White, "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); + } + } +} diff --git a/zone/bot_commands/pick_lock.cpp b/zone/bot_commands/pick_lock.cpp new file mode 100644 index 000000000..7c4379458 --- /dev/null +++ b/zone/bot_commands/pick_lock.cpp @@ -0,0 +1,58 @@ +#include "../bot_command.h" + +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(Chat::White, "usage: %s", sep->arg[0]); + c->Message(Chat::White, "requires one of the following bot classes:"); + c->Message(Chat::White, "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(Chat::White, "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(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(Chat::White, "%i door%s attempted - %i door%s successful", door_count, ((door_count != 1) ? ("s") : ("")), open_count, ((open_count != 1) ? ("s") : (""))); +} diff --git a/zone/bot_commands/pickpocket.cpp b/zone/bot_commands/pickpocket.cpp new file mode 100644 index 000000000..fe6715e2d --- /dev/null +++ b/zone/bot_commands/pickpocket.cpp @@ -0,0 +1,198 @@ +#include "../bot_command.h" + +void bot_command_pickpocket(Client *c, const Seperator *sep) +{ + if (helper_command_disabled(c, RuleB(Bots, AllowPickpocketCommand), "pickpocket")) { + return; + } + + if (helper_command_alias_fail(c, "bot_command_pickpocket", sep->arg[0], "pickpocket")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: ", sep->arg[0]); + return; + } + + std::list sbl; + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + + // Check for capable rogue + ActionableBots::Filter_ByClasses(c, sbl, player_class_bitmasks[Class::Rogue]); + Bot *my_bot = ActionableBots::AsSpawned_ByMinLevelAndClass(c, sbl, 7, Class::Rogue); + if (!my_bot) { + c->Message(Chat::White, "No bots are capable of performing this action"); + return; + } + + // Make sure a mob is targetted and a valid NPC + Mob *target_mob = ActionableTarget::AsSingle_ByAttackable(c); + if (!target_mob || !target_mob->IsNPC()) { + c->Message(Chat::White, "You must an enemy to use this command"); + return; + } + + NPC *target_npc = ActionableTarget::AsSingle_ByAttackable(c)->CastToNPC(); + + // Check if mob is close enough + glm::vec4 mob_distance = (c->GetPosition() - target_mob->GetPosition()); + float mob_xy_distance = ((mob_distance.x * mob_distance.x) + (mob_distance.y * mob_distance.y)); + float mob_z_distance = (mob_distance.z * mob_distance.z); + float z_offset_diff = target_mob->GetZOffset() - c->GetZOffset(); + + if (mob_z_distance >= (35-z_offset_diff) || mob_xy_distance > 250) { + c->Message(Chat::White, "You must be closer to an enemy to use this command"); + return; + } + + // Adapted from pickpock skill in npc.cpp + // Make sure we are allowed to target them + uint8 over_level = target_mob->GetLevel(); + if (over_level > (my_bot->GetLevel() + THIEF_PICKPOCKET_OVER)) { + c->Message(Chat::Red, "You are too inexperienced to pick pocket this target"); + return; + } + + // Random fail roll + if (zone->random.Roll(5)) { + if (zone->CanDoCombat()) { + target_mob->AddToHateList(c, 50); + } + target_mob->Say("Stop thief!"); + c->Message(Chat::Red, "You are noticed trying to steal!"); + return; + } + + // Setup variables for calcs + bool steal_skill = my_bot->GetSkill(EQ::skills::SkillPickPockets); + bool steal_chance = steal_skill * 100 / (5 * over_level + 5); + + // Determine whether to steal money or an item. + uint32 money[6] = { + 0, + ((steal_skill >= 125) ? (target_npc->GetPlatinum()) : (0)), + ((steal_skill >= 60) ? (target_npc->GetGold()) : (0)), + target_npc->GetSilver(), + target_npc->GetCopper(), + 0 + }; + + bool has_coin = ((money[PickPocketPlatinum] | money[PickPocketGold] | money[PickPocketSilver] | money[PickPocketCopper]) != 0); + bool steal_item = (steal_skill >= steal_chance && (zone->random.Roll(50) || !has_coin)); + + // Steal item + while (steal_item) { + std::vector> loot_selection; // + for (auto item_iter: target_npc->itemlist) { + if (!item_iter || !item_iter->item_id) { + continue; + } + auto item_test = database.GetItem(item_iter->item_id); + if (item_test->Magic || !item_test->NoDrop || item_test->IsClassBag() || c->CheckLoreConflict(item_test) || + item_iter->equip_slot != EQ::invslot::SLOT_INVALID) { + continue; + } + loot_selection.emplace_back( + std::make_pair( + item_test, + ((item_test->Stackable) ? (1) : (item_iter->charges)) + ) + ); + } + if (loot_selection.empty()) { + steal_item = false; + break; + } + + int random = zone->random.Int(0, (loot_selection.size() - 1)); + + int16 slot_id = c->GetInv().FindFreeSlot( + false, + true, + (loot_selection[random].first->Size), + (loot_selection[random].first->ItemType == EQ::item::ItemTypeArrow) + ); + if (slot_id == INVALID_INDEX) { + steal_item = false; + break; + } + + auto item_inst = database.CreateItem(loot_selection[random].first, loot_selection[random].second); + if (item_inst == nullptr) { + steal_item = false; + break; + } + + // Successful item pickpocket + if (item_inst->IsStackable() && RuleB(Character, UseStackablePickPocketing)) { + if (!c->TryStacking(item_inst, ItemPacketTrade, false, false)) { + c->PutItemInInventory(slot_id, *item_inst); + c->SendItemPacket(slot_id, item_inst, ItemPacketTrade); + } + } + else { + c->PutItemInInventory(slot_id, *item_inst); + c->SendItemPacket(slot_id, item_inst, ItemPacketTrade); + } + target_npc->RemoveItem(item_inst->GetID()); + c->Message(Chat::White, "You stole an item."); + safe_delete(item_inst); + return; + } + + // no items, try money + while (!steal_item && has_coin) { + uint32 coin_amount = zone->random.Int(1, (steal_skill / 25) + 1); + + int coin_type = PickPocketPlatinum; + while (coin_type <= PickPocketCopper) { + if (money[coin_type]) { + if (coin_amount > money[coin_type]) { + coin_amount = money[coin_type]; + } + break; + } + ++coin_type; + } + if (coin_type > PickPocketCopper) { + break; + } + + memset(money, 0, (sizeof(int) * 6)); + money[coin_type] = coin_amount; + + if (zone->random.Roll(steal_chance)) { // Successful coin pickpocket + switch (coin_type) { + case PickPocketPlatinum: + target_npc->SetPlatinum(target_npc->GetPlatinum() - coin_amount); + break; + case PickPocketGold: + target_npc->SetGold(target_npc->GetGold() - coin_amount); + break; + case PickPocketSilver: + target_npc->SetSilver(target_npc->GetSilver() - coin_amount); + break; + case PickPocketCopper: + target_npc->SetCopper(target_npc->GetCopper() - coin_amount); + break; + default: // has_coin..but, doesn't have coin? + c->Message(Chat::Red, "You failed to pickpocket."); + return; + } + c->Message(Chat::White, "You stole money."); + c->AddMoneyToPP( + money[PickPocketCopper], + money[PickPocketSilver], + money[PickPocketGold], + money[PickPocketPlatinum], + true + ); + return; + } + + c->Message(Chat::Red, "You failed to pickpocket."); + return; + } + c->Message(Chat::White, "This target's pockets are empty"); +} diff --git a/zone/bot_commands/precombat.cpp b/zone/bot_commands/precombat.cpp new file mode 100644 index 000000000..86ac7569a --- /dev/null +++ b/zone/bot_commands/precombat.cpp @@ -0,0 +1,33 @@ +#include "../bot_command.h" + +void bot_command_precombat(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_precombat", sep->arg[0], "precombat")) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + + c->Message(Chat::White, "usage: %s ([set | clear])", sep->arg[0]); + return; + } + + if (!c->GetTarget() || !c->IsAttackAllowed(c->GetTarget())) { + + c->Message(Chat::White, "This command requires an attackable target."); + return; + } + + std::string argument(sep->arg[1]); + + if (!argument.compare("set")) { + c->SetBotPrecombat(true); + } + else if (!argument.compare("clear")) { + c->SetBotPrecombat(false); + } + else { + c->SetBotPrecombat(!c->GetBotPrecombat()); + } + + c->Message(Chat::White, "Precombat flag is now %s.", (c->GetBotPrecombat() ? "set" : "clear")); +} diff --git a/zone/bot_commands/pull.cpp b/zone/bot_commands/pull.cpp new file mode 100644 index 000000000..ba332b359 --- /dev/null +++ b/zone/bot_commands/pull.cpp @@ -0,0 +1,117 @@ +#include "../bot_command.h" + +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(Chat::White, "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(Chat::White, "Your current target is not attackable!"); + return; + } + + if (target_mob->IsNPC() && target_mob->GetHateList().size()) { + + c->Message(Chat::White, "Your current target is already engaged!"); + return; + } + + Bot* bot_puller = nullptr; + for (auto bot_iter : sbl) { + + if (bot_iter->GetAppearance() == eaDead || bot_iter->GetBotStance() == EQ::constants::stancePassive) { + continue; + } + + switch (bot_iter->GetClass()) { + case Class::Rogue: + case Class::Monk: + case Class::Bard: + case Class::Ranger: + bot_puller = bot_iter; + break; + case Class::Warrior: + case Class::ShadowKnight: + case Class::Paladin: + case Class::Berserker: + case Class::Beastlord: + if (!bot_puller) { + + bot_puller = bot_iter; + continue; + } + + switch (bot_puller->GetClass()) { + case Class::Druid: + case Class::Shaman: + case Class::Cleric: + case Class::Wizard: + case Class::Necromancer: + case Class::Magician: + case Class::Enchanter: + bot_puller = bot_iter; + default: + continue; + } + + continue; + case Class::Druid: + case Class::Shaman: + case Class::Cleric: + if (!bot_puller) { + + bot_puller = bot_iter; + continue; + } + + switch (bot_puller->GetClass()) { + case Class::Wizard: + case Class::Necromancer: + case Class::Magician: + case Class::Enchanter: + bot_puller = bot_iter; + default: + continue; + } + + continue; + case Class::Wizard: + case Class::Necromancer: + case Class::Magician: + case Class::Enchanter: + if (!bot_puller) { + bot_puller = bot_iter; + } + + continue; + default: + continue; + } + + + bot_puller = bot_iter; + + break; + } + + if (bot_puller) { + bot_puller->SetPullFlag(); + } + + helper_no_available_bots(c, bot_puller); +} diff --git a/zone/bot_commands/release.cpp b/zone/bot_commands/release.cpp new file mode 100644 index 000000000..1872d43d6 --- /dev/null +++ b/zone/bot_commands/release.cpp @@ -0,0 +1,24 @@ +#include "../bot_command.h" + +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(Chat::White, "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(Chat::White, "%i of your bots %s released.", sbl.size(), ((sbl.size() != 1) ? ("are") : ("is"))); +} diff --git a/zone/bot_commands/report.cpp b/zone/bot_commands/report.cpp new file mode 100644 index 000000000..dc2b7d868 --- /dev/null +++ b/zone/bot_commands/report.cpp @@ -0,0 +1 @@ +#include "../bot_command.h" diff --git a/zone/bot_commands/resistance.cpp b/zone/bot_commands/resistance.cpp new file mode 100644 index 000000000..0b84b4b88 --- /dev/null +++ b/zone/bot_commands/resistance.cpp @@ -0,0 +1,73 @@ +#include "../bot_command.h" + +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(Chat::White, "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(Chat::White, "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); +} diff --git a/zone/bot_commands/resurrect.cpp b/zone/bot_commands/resurrect.cpp new file mode 100644 index 000000000..0b15ef3e1 --- /dev/null +++ b/zone/bot_commands/resurrect.cpp @@ -0,0 +1,57 @@ +#include "../bot_command.h" + +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(Chat::White, "usage: %s ([option: aoe])", sep->arg[0]); + c->Message(Chat::White, "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); +} diff --git a/zone/bot_commands/rune.cpp b/zone/bot_commands/rune.cpp new file mode 100644 index 000000000..71b1cf572 --- /dev/null +++ b/zone/bot_commands/rune.cpp @@ -0,0 +1,38 @@ +#include "../bot_command.h" + +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(Chat::White, "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); +} diff --git a/zone/bot_commands/send_home.cpp b/zone/bot_commands/send_home.cpp new file mode 100644 index 000000000..6950e2bee --- /dev/null +++ b/zone/bot_commands/send_home.cpp @@ -0,0 +1,47 @@ +#include "../bot_command.h" + +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(Chat::White, "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); +} diff --git a/zone/bot_commands/size.cpp b/zone/bot_commands/size.cpp new file mode 100644 index 000000000..69e2fd1a2 --- /dev/null +++ b/zone/bot_commands/size.cpp @@ -0,0 +1,50 @@ +#include "../bot_command.h" + +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(Chat::White, "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(Chat::White, "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); +} diff --git a/zone/bot_commands/spell.cpp b/zone/bot_commands/spell.cpp new file mode 100644 index 000000000..45d9ec6eb --- /dev/null +++ b/zone/bot_commands/spell.cpp @@ -0,0 +1,570 @@ +#include "../bot_command.h" + +void bot_command_spell_list(Client* c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_list", sep->arg[0], "spells")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Min Level] (Level is optional)", + sep->arg[0] + ).c_str() + ); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must target a bot that you own to use this command."); + return; + } + + uint8 min_level = 0; + + if (sep->IsNumber(1)) { + min_level = static_cast(Strings::ToUnsignedInt(sep->arg[1])); + } + + my_bot->ListBotSpells(min_level); +} + +void bot_command_spell_settings_add(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_settings_add", sep->arg[0], "spellsettingsadd")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Spell ID] [Priority] [Min HP] [Max HP]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must target a bot that you own to use this command."); + return; + } + + auto arguments = sep->argnum; + if ( + arguments < 4 || + !sep->IsNumber(1) || + !sep->IsNumber(2) || + !sep->IsNumber(3) || + !sep->IsNumber(4) + ) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Spell ID] [Priority] [Min HP] [Max HP]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto spell_id = static_cast(Strings::ToUnsignedInt(sep->arg[1])); + + if (!IsValidSpell(spell_id)) { + c->Message( + Chat::White, + fmt::format( + "Spell ID {} is invalid or could not be found.", + spell_id + ).c_str() + ); + return; + } + + if (my_bot->GetBotSpellSetting(spell_id)) { + c->Message( + Chat::White, + fmt::format( + "{} already has a spell setting for {} ({}), trying using {} instead.", + my_bot->GetCleanName(), + spells[spell_id].name, + spell_id, + Saylink::Silent("^spellsettingsupdate") + ).c_str() + ); + return; + } + + auto priority = static_cast(Strings::ToInt(sep->arg[2])); + auto min_hp = static_cast(EQ::Clamp(Strings::ToInt(sep->arg[3]), -1, 99)); + auto max_hp = static_cast(EQ::Clamp(Strings::ToInt(sep->arg[4]), -1, 100)); + + BotSpellSetting bs; + + bs.priority = priority; + bs.min_hp = min_hp; + bs.max_hp = max_hp; + + if (!my_bot->AddBotSpellSetting(spell_id, &bs)) { + c->Message( + Chat::White, + fmt::format( + "Failed to add spell setting for {}.", + my_bot->GetCleanName() + ).c_str() + ); + return; + } + + my_bot->AI_AddBotSpells(my_bot->GetBotSpellID()); + + c->Message( + Chat::White, + fmt::format( + "Successfully added spell setting for {}.", + my_bot->GetCleanName() + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Spell Setting Added | Spell: {} ({}) ", + spells[spell_id].name, + spell_id + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Spell Setting Added | Priority: {} Health: {}", + priority, + my_bot->GetHPString(min_hp, max_hp) + ).c_str() + ); +} + +void bot_command_spell_settings_delete(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_settings_delete", sep->arg[0], "spellsettingsdelete")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Spell ID]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must target a bot that you own to use this command."); + return; + } + + auto arguments = sep->argnum; + if ( + arguments < 1 || + !sep->IsNumber(1) + ) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Spell ID]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto spell_id = static_cast(Strings::ToUnsignedInt(sep->arg[1])); + + if (!IsValidSpell(spell_id)) { + c->Message( + Chat::White, + fmt::format( + "Spell ID {} is invalid or could not be found.", + spell_id + ).c_str() + ); + return; + } + + if (!my_bot->DeleteBotSpellSetting(spell_id)) { + c->Message( + Chat::White, + fmt::format( + "Failed to delete spell setting for {}.", + my_bot->GetCleanName() + ).c_str() + ); + return; + } + + my_bot->AI_AddBotSpells(my_bot->GetBotSpellID()); + + c->Message( + Chat::White, + fmt::format( + "Successfully deleted spell setting for {}.", + my_bot->GetCleanName() + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Spell Setting Deleted | Spell: {} ({})", + spells[spell_id].name, + spell_id + ).c_str() + ); +} + +void bot_command_spell_settings_list(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_settings_list", sep->arg[0], "spellsettings")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message( + Chat::White, + fmt::format( + "Usage: {}", + sep->arg[0] + ).c_str() + ); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must target a bot that you own to use this command."); + return; + } + + my_bot->ListBotSpellSettings(); +} + +void bot_command_spell_settings_toggle(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_settings_toggle", sep->arg[0], "spellsettingstoggle")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Spell ID] [Toggle]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must target a bot that you own to use this command."); + return; + } + + auto arguments = sep->argnum; + if ( + arguments < 2 || + !sep->IsNumber(1) + ) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Spell ID] [Toggle]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto spell_id = static_cast(Strings::ToUnsignedInt(sep->arg[1])); + if (!IsValidSpell(spell_id)) { + c->Message( + Chat::White, + fmt::format( + "Spell ID {} is invalid or could not be found.", + spell_id + ).c_str() + ); + return; + } + + bool toggle = ( + sep->IsNumber(2) ? + Strings::ToInt(sep->arg[2]) != 0 : + atobool(sep->arg[2]) + ); + + auto obs = my_bot->GetBotSpellSetting(spell_id); + if (!obs) { + return; + } + + BotSpellSetting bs; + + bs.priority = obs->priority; + bs.min_hp = obs->min_hp; + bs.max_hp = obs->max_hp; + bs.is_enabled = toggle; + + if (!my_bot->UpdateBotSpellSetting(spell_id, &bs)) { + c->Message( + Chat::White, + fmt::format( + "Failed to {}able spell for {}.", + toggle ? "en" : "dis", + my_bot->GetCleanName() + ).c_str() + ); + return; + } + + my_bot->AI_AddBotSpells(my_bot->GetBotSpellID()); + + c->Message( + Chat::White, + fmt::format( + "Successfully {}abled spell for {}.", + toggle ? "en" : "dis", + my_bot->GetCleanName() + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Spell {}abled | Spell: {} ({})", + toggle ? "En" : "Dis", + spells[spell_id].name, + spell_id + ).c_str() + ); +} + +void bot_command_spell_settings_update(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_spell_settings_update", sep->arg[0], "spellsettingsupdate")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Spell ID] [Priority] [Min HP] [Max HP]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must target a bot that you own to use this command."); + return; + } + + auto arguments = sep->argnum; + if ( + arguments < 4 || + !sep->IsNumber(1) || + !sep->IsNumber(2) || + !sep->IsNumber(3) || + !sep->IsNumber(4) + ) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Spell ID] [Priority] [Min HP] [Max HP]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto spell_id = static_cast(Strings::ToUnsignedInt(sep->arg[1])); + + if (!IsValidSpell(spell_id)) { + c->Message( + Chat::White, + fmt::format( + "Spell ID {} is invalid or could not be found.", + spell_id + ).c_str() + ); + return; + } + + auto priority = static_cast(Strings::ToInt(sep->arg[2])); + auto min_hp = static_cast(EQ::Clamp(Strings::ToInt(sep->arg[3]), -1, 99)); + auto max_hp = static_cast(EQ::Clamp(Strings::ToInt(sep->arg[4]), -1, 100)); + + BotSpellSetting bs; + + bs.priority = priority; + bs.min_hp = min_hp; + bs.max_hp = max_hp; + + if (!my_bot->UpdateBotSpellSetting(spell_id, &bs)) { + c->Message( + Chat::White, + fmt::format( + "Failed to update spell setting for {}.", + my_bot->GetCleanName() + ).c_str() + ); + return; + } + + my_bot->AI_AddBotSpells(my_bot->GetBotSpellID()); + + c->Message( + Chat::White, + fmt::format( + "Successfully updated spell setting for {}.", + my_bot->GetCleanName() + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Spell Setting Updated | Spell: {} ({})", + spells[spell_id].name, + spell_id + ).c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Spell Setting Updated | Priority: {} Health: {}", + priority, + my_bot->GetHPString(min_hp, max_hp) + ).c_str() + ); +} + +void bot_spell_info_dialogue_window(Client* c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_spell_info_dialogue_window", sep->arg[0], "spellinfo")) { + return; + } + + auto arguments = sep->argnum; + if ( + arguments < 1 || + !sep->IsNumber(1) + ) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [Spell ID]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must target a bot that you own to use this command."); + return; + } + + auto spell_id = static_cast(Strings::ToUnsignedInt(sep->arg[1])); + auto min_level = spells[spell_id].classes; + auto class_level = min_level[my_bot->GetBotClass() - 1]; + + if (class_level > my_bot->GetLevel()) { + c->Message(Chat::White, "This is not a usable spell by your bot."); + return; + } + + auto results = database.QueryDatabase( + fmt::format( + "SELECT value FROM db_str WHERE id = {} and type = 6 LIMIT 1", + spells[spell_id].effect_description_id + ) + ); + + if (!results.Success() || !results.RowCount()) { + c->Message(Chat::White, "No Spell Information Available for this."); + return; + } + + auto row = results.begin(); + std::string spell_desc = row[0]; + + auto m = DialogueWindow::TableRow( + DialogueWindow::TableCell("Spell Effect: ") + + DialogueWindow::TableCell(spell_desc) + ); + + m += DialogueWindow::TableRow( + DialogueWindow::TableCell("Spell Level: ") + + DialogueWindow::TableCell(fmt::format("{}", class_level)) + ); + + c->SendPopupToClient( + fmt::format( + "Spell: {}", spells[spell_id].name + ).c_str(), + DialogueWindow::Table(m).c_str() + ); +} + +void bot_command_enforce_spell_list(Client* c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_enforce_spell_list", sep->arg[0], "enforcespellsettings")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message( + Chat::White, + fmt::format( + "Usage: {} [True|False] (Blank to toggle]", + sep->arg[0] + ).c_str() + ); + return; + } + + auto my_bot = ActionableBots::AsTarget_ByBot(c); + if (!my_bot) { + c->Message(Chat::White, "You must target a bot that you own to use this command."); + return; + } + + bool enforce_state = (sep->argnum > 0) ? Strings::ToBool(sep->arg[1]) : !my_bot->GetBotEnforceSpellSetting(); + my_bot->SetBotEnforceSpellSetting(enforce_state, true); + + c->Message( + Chat::White, + fmt::format( + "{}'s Spell Settings List entries are now {}.", + my_bot->GetCleanName(), + my_bot->GetBotEnforceSpellSetting() ? "enforced" : "optional" + ).c_str() + ); +} diff --git a/zone/bot_commands/stance.cpp b/zone/bot_commands/stance.cpp new file mode 100644 index 000000000..dc2b7d868 --- /dev/null +++ b/zone/bot_commands/stance.cpp @@ -0,0 +1 @@ +#include "../bot_command.h" diff --git a/zone/bot_commands/stop_melee_level.cpp b/zone/bot_commands/stop_melee_level.cpp new file mode 100644 index 000000000..dc2b7d868 --- /dev/null +++ b/zone/bot_commands/stop_melee_level.cpp @@ -0,0 +1 @@ +#include "../bot_command.h" diff --git a/zone/bot_commands/suffix.cpp b/zone/bot_commands/suffix.cpp new file mode 100644 index 000000000..dc2b7d868 --- /dev/null +++ b/zone/bot_commands/suffix.cpp @@ -0,0 +1 @@ +#include "../bot_command.h" diff --git a/zone/bot_commands/summon.cpp b/zone/bot_commands/summon.cpp new file mode 100644 index 000000000..dc2b7d868 --- /dev/null +++ b/zone/bot_commands/summon.cpp @@ -0,0 +1 @@ +#include "../bot_command.h" diff --git a/zone/bot_commands/summon_corpse.cpp b/zone/bot_commands/summon_corpse.cpp new file mode 100644 index 000000000..2a7a5b80f --- /dev/null +++ b/zone/bot_commands/summon_corpse.cpp @@ -0,0 +1,47 @@ +#include "../bot_command.h" + +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(Chat::White, "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(Chat::White, "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_value[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); +} diff --git a/zone/bot_commands/surname.cpp b/zone/bot_commands/surname.cpp new file mode 100644 index 000000000..dc2b7d868 --- /dev/null +++ b/zone/bot_commands/surname.cpp @@ -0,0 +1 @@ +#include "../bot_command.h" diff --git a/zone/bot_commands/suspend.cpp b/zone/bot_commands/suspend.cpp new file mode 100644 index 000000000..e3198e79e --- /dev/null +++ b/zone/bot_commands/suspend.cpp @@ -0,0 +1,25 @@ +#include "../bot_command.h" + +void bot_command_suspend(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_command_suspend", sep->arg[0], "suspend")) { + return; + } + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "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(Chat::White, "%i of your bots %s suspended.", sbl.size(), ((sbl.size() != 1) ? ("are") : ("is"))); +} diff --git a/zone/bot_commands/taunt.cpp b/zone/bot_commands/taunt.cpp new file mode 100644 index 000000000..a15d29a5b --- /dev/null +++ b/zone/bot_commands/taunt.cpp @@ -0,0 +1,118 @@ +#include "../bot_command.h" + +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(Chat::White, "usage: %s ([option: on | off]) ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | byclass | byrace | 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::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[(ab_arg + 1)] : nullptr, class_race_check ? atoi(sep->arg[(ab_arg + 1)]) : 0) == ActionableBots::ABT_None) { + return; + } + sbl.remove(nullptr); + + int taunting_count = 0; + for (auto bot_iter : sbl) { + if (!bot_iter->GetSkill(EQ::skills::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, + fmt::format( + "I am {} taunting.", + bot_iter->IsTaunting() ? "now" : "no longer" + ).c_str() + ); + } + + ++taunting_count; + } + + for (auto bot_iter : sbl) { + if (!bot_iter->HasPet()) { + continue; + } + + if (!bot_iter->GetPet()->GetSkill(EQ::skills::SkillTaunt)) { + continue; + } + + if (toggle_taunt) { + bot_iter->GetPet()->CastToNPC()->SetTaunting(!bot_iter->GetPet()->CastToNPC()->IsTaunting()); + } else { + bot_iter->GetPet()->CastToNPC()->SetTaunting(taunt_state); + } + + if (sbl.size() == 1) { + Bot::BotGroupSay( + bot_iter, + fmt::format( + "My Pet is {} taunting.", + bot_iter->GetPet()->CastToNPC()->IsTaunting() ? "now" : "no longer" + ).c_str() + ); + } + + ++taunting_count; + } + + if (taunting_count) { + if (toggle_taunt) { + c->Message( + Chat::White, + fmt::format( + "{} of your bots and their pets {} toggled their taunting state", + taunting_count, + taunting_count != 1 ? "have" : "has" + ).c_str() + ); + } else { + c->Message( + Chat::White, + fmt::format( + "{} of your bots and their pets {} {} taunting.", + taunting_count, + taunting_count != 1 ? "have" : "has", + taunt_state ? "started" : "stopped" + ).c_str() + ); + } + } + else { + c->Message(Chat::White, "None of your bots are capable of taunting"); + } +} diff --git a/zone/bot_commands/teleport.cpp b/zone/bot_commands/teleport.cpp new file mode 100644 index 000000000..94e8ee2b0 --- /dev/null +++ b/zone/bot_commands/teleport.cpp @@ -0,0 +1,119 @@ +#include "../bot_command.h" + +void bot_command_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_command_circle", sep->arg[0], "circle")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [list | destination] ([option: single])", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Depart, Class::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, Class::Druid); + helper_command_depart_list(c, my_druid_bot, nullptr, local_list, single); + return; + } + else if (destination.empty()) { + c->Message(Chat::White, "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 != 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_command_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_command_portal", sep->arg[0], "portal")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [list | destination] ([option: single])", sep->arg[0]); + helper_send_usage_required_bots(c, BCEnum::SpT_Depart, Class::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, Class::Wizard); + helper_command_depart_list(c, nullptr, my_wizard_bot, local_list, single); + return; + } + else if (destination.empty()) { + c->Message(Chat::White, "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 != 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); +} diff --git a/zone/bot_commands/timer.cpp b/zone/bot_commands/timer.cpp new file mode 100644 index 000000000..93eab687d --- /dev/null +++ b/zone/bot_commands/timer.cpp @@ -0,0 +1,189 @@ +#include "../bot_command.h" + +void bot_command_timer(Client* c, const Seperator* sep) +{ + if (helper_command_alias_fail(c, "bot_command_timer", sep->arg[0], "timer")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(Chat::White, "usage: %s [clear | has | set] [disc | item | spell] [timer ID | item ID | spell ID | all] [optional ms for set] [actionable].", sep->arg[0]); + c->Message(Chat::White, "When setting, you can leave the value blank to use the default for the item or specify a value in ms to set the timer to."); + c->Message(Chat::White, "Returns or sets the provided timer(s) for the selected bot(s) or clears the selected timer(s) for the selected bot(s)."); + return; + } + + const int ab_mask = ActionableBots::ABM_Type1; + + std::string arg1 = sep->arg[1]; + std::string arg2 = sep->arg[2]; + std::string arg3 = sep->arg[3]; + int ab_arg = 4; + bool clear = false; + bool has = false; + bool set = false; + bool disc = false; + bool item = false; + bool spell = false; + uint32 timer_id = 0; + uint32 timer_value = 0; + bool all = false; + + if (!arg1.compare("clear")) { + clear = true; + } + else if (!arg1.compare("has")) { + has = true; + } + else if (!arg1.compare("set")) { + set = true; + } + else { + c->Message(Chat::White, "Incorrect argument, use %s help for a list of options.", sep->arg[0]); + return; + } + + if (!arg2.compare("disc")) { + disc = true; + } + else if (!arg2.compare("item")) { + item = true; + } + else if (!arg2.compare("spell")) { + spell = true; + } + else { + c->Message(Chat::White, "Incorrect timer type, use %s help for a list of options.", sep->arg[0]); + return; + } + + if (sep->IsNumber(3)) { + timer_id = atoi(sep->arg[3]); + if (timer_id < 0) { + c->Message(Chat::White, "You cannot use negative numbers."); + return; + } + } + else if (!arg3.compare("all")) { + if (has || set) { + c->Message(Chat::White, "You can only use 'all' for clearing timers."); + return; + } + + all = true; + } + else { + c->Message(Chat::White, "Incorrect ID option, use %s help for a list of options.", sep->arg[0]); + return; + } + + if (set) { + if (sep->IsNumber(4)) { + ab_arg = 5; + timer_value = atoi(sep->arg[4]); + if (timer_value <= 0) { + c->Message(Chat::White, "You cannot use 0 or negative numbers."); + return; + } + } + } + + std::string class_race_arg = sep->arg[ab_arg]; + bool class_race_check = false; + if (!class_race_arg.compare("byclass") || !class_race_arg.compare("byrace")) { + class_race_check = true; + } + + std::list sbl; + if (ActionableBots::PopulateSBL(c, sep->arg[ab_arg], sbl, ab_mask, !class_race_check ? sep->arg[ab_arg + 1] : nullptr, class_race_check ? atoi(sep->arg[ab_arg + 1]) : 0) == ActionableBots::ABT_None) { + return; + } + sbl.remove(nullptr); + + for (auto my_bot : sbl) { + bool found = false; + + if (clear) { + c->Message( + Chat::White, + fmt::format( + "{} says, 'Clearing {} timer{}'", + my_bot->GetCleanName(), + disc ? "Discipline" : item ? "Item" : "Spell", + (all ? "s." : ".") + ).c_str() + ); + + if (disc) { + my_bot->ClearDisciplineReuseTimer(timer_id); + } + else if (item) { + my_bot->ClearItemReuseTimer(timer_id); + } + else if (spell) { + my_bot->ClearSpellRecastTimer(timer_id); + } + } + else if (has) { + uint32 remaining_time; + std::string time_string = ""; + + if (disc) { + if (!my_bot->CheckDisciplineReuseTimer(timer_id)) { + remaining_time = my_bot->GetDisciplineReuseRemainingTime(timer_id) / 1000; + time_string = Strings::SecondsToTime(remaining_time); + found = true; + } + } + else if (item) { + if (!my_bot->CheckItemReuseTimer(timer_id)) { + remaining_time = my_bot->GetItemReuseRemainingTime(timer_id) / 1000; + time_string = Strings::SecondsToTime(remaining_time); + found = true; + } + } + else if (spell) { + if (!my_bot->CheckSpellRecastTimer(timer_id)) { + remaining_time = my_bot->GetSpellRecastRemainingTime(timer_id) / 1000; + time_string = Strings::SecondsToTime(remaining_time); + found = true; + } + } + + c->Message( + Chat::White, + fmt::format( + "{} says, 'I {}{}{}'", + my_bot->GetCleanName(), + (!found ? " do not have that timer currently" : " have "), + (!found ? "" : time_string), + (!found ? "." : " remaining.") + ).c_str() + ); + } + else if (set) { + c->Message( + Chat::White, + fmt::format( + "{} says, 'Setting {} timer{} for {} to {}.'", + my_bot->GetCleanName(), + disc ? "Discipline" : item ? "Item" : "Spell", + (all ? "s" : ""), + timer_id, + timer_value ? std::to_string(timer_value) : "the default value" + ).c_str() + ); + + if (disc) { + my_bot->ClearDisciplineReuseTimer(timer_id); + my_bot->SetDisciplineReuseTimer(timer_id, timer_value); + } + else if (item) { + my_bot->ClearItemReuseTimer(timer_id); + my_bot->SetItemReuseTimer(timer_id, timer_value); + } + else if (spell) { + my_bot->ClearSpellRecastTimer(timer_id); + my_bot->SetSpellRecastTimer(timer_id, timer_value); + } + } + } +} diff --git a/zone/bot_commands/toggle_archer.cpp b/zone/bot_commands/toggle_archer.cpp new file mode 100644 index 000000000..dc2b7d868 --- /dev/null +++ b/zone/bot_commands/toggle_archer.cpp @@ -0,0 +1 @@ +#include "../bot_command.h" diff --git a/zone/bot_commands/toggle_helm.cpp b/zone/bot_commands/toggle_helm.cpp new file mode 100644 index 000000000..dc2b7d868 --- /dev/null +++ b/zone/bot_commands/toggle_helm.cpp @@ -0,0 +1 @@ +#include "../bot_command.h" diff --git a/zone/bot_commands/track.cpp b/zone/bot_commands/track.cpp new file mode 100644 index 000000000..9ac0904a2 --- /dev/null +++ b/zone/bot_commands/track.cpp @@ -0,0 +1,72 @@ +#include "../bot_command.h" + +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(Chat::White, "usage: %s (Ranger: [option=all: all | rare | local])", sep->arg[0]); + c->Message(Chat::White, "requires one of the following bot classes:"); + c->Message(Chat::White, "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_bitmasks[Class::Ranger] | player_class_bitmasks[Class::Druid] | player_class_bitmasks[Class::Bard]); + ActionableBots::Filter_ByClasses(c, sbl, class_mask); + + Bot* my_bot = ActionableBots::AsSpawned_ByMinLevelAndClass(c, sbl, 1, Class::Ranger); + if (tracking_scope.empty()) { + if (!my_bot) + my_bot = ActionableBots::AsSpawned_ByMinLevelAndClass(c, sbl, 20, Class::Druid); + if (!my_bot) + my_bot = ActionableBots::AsSpawned_ByMinLevelAndClass(c, sbl, 35, Class::Bard); + } + if (!my_bot) { + c->Message(Chat::White, "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 Class::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 Class::Druid: + base_distance = 30; + tracking_msg = "Local tracking..."; + break; + case Class::Bard: + base_distance = 20; + tracking_msg = "Near tracking..."; + break; + default: + return; + } + if (!base_distance) { + c->Message(Chat::White, "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); +} diff --git a/zone/bot_commands/update.cpp b/zone/bot_commands/update.cpp new file mode 100644 index 000000000..dc2b7d868 --- /dev/null +++ b/zone/bot_commands/update.cpp @@ -0,0 +1 @@ +#include "../bot_command.h" diff --git a/zone/bot_commands/view_combos.cpp b/zone/bot_commands/view_combos.cpp new file mode 100644 index 000000000..7a44e5380 --- /dev/null +++ b/zone/bot_commands/view_combos.cpp @@ -0,0 +1,116 @@ +#include "../bot_command.h" + +void bot_command_view_combos(Client *c, const Seperator *sep) +{ + const std::string class_substrs[17] = { + "", + "WAR", "CLR", "PAL", "RNG", + "SHD", "DRU", "MNK", "BRD", + "ROG", "SHM", "NEC", "WIZ", + "MAG", "ENC", "BST", "BER" + }; + + const std::string race_substrs[17] = { + "", + "HUM", "BAR", "ERU", "ELF", + "HIE", "DEF", "HEF", "DWF", + "TRL", "OGR", "HFL", "GNM", + "IKS", "VAH", "FRG", "DRK" + }; + + const uint16 race_values[17] = { + Race::Doug, + Race::Human, Race::Barbarian, Race::Erudite, Race::WoodElf, + Race::HighElf, Race::DarkElf, Race::HalfElf, Race::Dwarf, + Race::Troll, Race::Ogre, Race::Halfling, Race::Gnome, + Race::Iksar, Race::VahShir, Race::Froglok2, Race::Drakkin + }; + + if (helper_command_alias_fail(c, "bot_command_view_combos", sep->arg[0], "viewcombos")) { + return; + } + + if (helper_is_help_or_usage(sep->arg[1])) { + std::string window_text; + std::string message_separator = " "; + c->Message(Chat::White, fmt::format("Usage: {} [Race]", sep->arg[0]).c_str()); + + window_text.append(""); + + for (int race_id = 0; race_id <= 15; ++race_id) { + window_text.append(message_separator); + window_text.append( + fmt::format( + "{} ({})", + race_substrs[race_id + 1], + race_values[race_id + 1] + ) + ); + + message_separator = ", "; + } + c->SendPopupToClient("Bot Races", window_text.c_str()); + return; + } + + if (sep->arg[1][0] == '\0' || !sep->IsNumber(1)) { + c->Message(Chat::White, "Invalid Race!"); + return; + } + + const uint16 bot_race = static_cast(Strings::ToUnsignedInt(sep->arg[1])); + const std::string race_name = GetRaceIDName(bot_race); + + if (!IsPlayerRace(bot_race)) { + c->Message( + Chat::White, + fmt::format( + "{} ({}) is not a race bots can use.", + race_name, + bot_race + ).c_str() + ); + return; + } + + const auto classes_bitmask = database.botdb.GetRaceClassBitmask(bot_race); + + std::string window_text; + std::string message_separator = " "; + + window_text.append(""); + + const int object_max = 4; + auto object_count = 0; + + for (int class_id = 0; class_id <= 15; ++class_id) { + if (classes_bitmask & GetPlayerClassBit(class_id)) { + window_text.append(message_separator); + + if (object_count >= object_max) { + window_text.append(DialogueWindow::Break()); + object_count = 0; + } + + window_text.append( + fmt::format( + "{} ({})", + class_substrs[class_id], + class_id + ) + ); + + ++object_count; + message_separator = ", "; + } + } + + c->SendPopupToClient( + fmt::format( + "Bot Classes for {} ({})", + race_name, + bot_race + ).c_str(), + window_text.c_str() + ); +} diff --git a/zone/bot_commands/water_breathing.cpp b/zone/bot_commands/water_breathing.cpp new file mode 100644 index 000000000..cb9b792c4 --- /dev/null +++ b/zone/bot_commands/water_breathing.cpp @@ -0,0 +1,38 @@ +#include "../bot_command.h" + +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(Chat::White, "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); +}