diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 650f4a5e8..1b00d701a 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -1290,7 +1290,7 @@ int bot_command_init(void) bot_command_add("copysettings", "Copies settings from one bot to another", AccountStatus::Player, bot_command_copy_settings) || bot_command_add("defaultsettings", "Restores a bot back to default settings", AccountStatus::Player, bot_command_default_settings) || bot_command_add("defensive", "Orders a bot to use a defensive discipline", AccountStatus::Player, bot_command_defensive) || - bot_command_add("depart", "Orders a bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_command_depart) || //TODO bot rewrite - deleteme + bot_command_add("depart", "Orders a bot to open a magical doorway to a specified destination", AccountStatus::Player, bot_command_depart) || bot_command_add("enforcespellsettings", "Toggles your Bot to cast only spells in their spell settings list.", AccountStatus::Player, bot_command_enforce_spell_list) || bot_command_add("findaliases", "Find available aliases for a bot command", AccountStatus::Player, bot_command_find_aliases) || bot_command_add("follow", "Orders bots to follow a designated target (option 'chain' auto-links eligible spawned bots)", AccountStatus::Player, bot_command_follow) || diff --git a/zone/bot_commands/depart.cpp b/zone/bot_commands/depart.cpp index d9b09d2d4..80c3c4e22 100644 --- a/zone/bot_commands/depart.cpp +++ b/zone/bot_commands/depart.cpp @@ -2,102 +2,296 @@ 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")) { + if (helper_is_help_or_usage(sep->arg[1])) { + std::vector description = + { + "Tells bots to list their port locations or port to a specific location" + }; + + std::vector notes = + { + "- This will interrupt any spell currently being cast by bots told to use the command.", + "- Bots will still check to see if they have the spell in their spell list, whether the target is immune, spell is allowed and all other sanity checks for spells" + }; + + std::vector example_format = + { + fmt::format( + "{} [list | zone shortname] [optional: single | group | ae] [actionable, default: spawned]" + , sep->arg[0] + ) + }; + std::vector examples_one = + { + "To tell everyone to list their portable locations:", + fmt::format( + "{} list spawned", + sep->arg[0] + ) + }; + std::vector examples_two = + { + "To tell all bots to port to Nexus:", + fmt::format( + "{} nexus spawned", + sep->arg[0] + ) + }; + std::vector examples_three = + { + "To tell Druidbot to single target port to Butcher:", + fmt::format( + "{} butcher single byname Druidbot", + sep->arg[0] + ) + }; + + std::vector actionables = + { + "target, byname, ownergroup, ownerraid, targetgroup, namesgroup, healrotationtargets mmr, byclass, byrace, spawned" + }; + + std::vector options = { }; + std::vector options_one = { }; + std::vector options_two = { }; + std::vector options_three = { }; + + std::string popup_text = c->SendCommandHelpWindow( + c, + description, + notes, + example_format, + examples_one, examples_two, examples_three, + actionables, + options, + options_one, options_two, options_three + ); + + popup_text = DialogueWindow::Table(popup_text); + + c->SendPopupToClient(sep->arg[0], popup_text.c_str()); + + if (RuleB(Bots, SendClassRaceOnHelp)) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information about race/class IDs.", + Saylink::Silent("^classracelist") + ).c_str() + ); + } + 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); + bool single = false; + bool group = true; + bool ae = false; + std::string single_arg = sep->arg[2]; + bool list = false; + std::string destination = sep->arg[1]; + int ab_arg = 2; + + if (!single_arg.compare("single")) { + ++ab_arg; + single = true; + group = false; + } + else if (!single_arg.compare("group")) { + ++ab_arg; + single = false; + group = true; + } + else if (!single_arg.compare("ae")) { + ++ab_arg; + single = false; + group = false; + ae = true; + } - return; + if (!destination.compare("list") || destination.empty()) { + list = true; + + if (destination.empty()) { + c->Message( + Chat::Yellow, + fmt::format( + "Use {} for information regarding this command.", + Saylink::Silent( + fmt::format("{} help", sep->arg[0]) + ) + ).c_str() + ); + } + } + + Mob* tar = c->GetTarget(); + + if (!tar) { + tar = c; + } + + std::string argString = sep->arg[ab_arg]; + + const int ab_mask = ActionableBots::ABM_Type1; + std::string actionableArg = sep->arg[ab_arg]; + + if (actionableArg.empty()) { + actionableArg = "spawned"; + } + + 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; - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - ActionableTarget::Types actionable_targets; - Bot* my_bot = nullptr; 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::Select_ByMinLevelAndClass(c, BCEnum::TT_None, sbl, 1, Class::Druid); - Bot* my_wizard_bot = ActionableBots::Select_ByMinLevelAndClass(c, BCEnum::TT_None, sbl, 1, Class::Wizard); - - if ( - (!my_druid_bot && !my_wizard_bot) || - (my_druid_bot && !my_druid_bot->IsInGroupOrRaid(c)) || - (my_wizard_bot && !my_wizard_bot->IsInGroupOrRaid(c)) - ) { - c->Message(Chat::Yellow, "No compatible bots found for %s.", sep->arg[0]); - return; - } - - 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"); + if (ActionableBots::PopulateSBL(c, actionableArg, 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; } - my_bot = nullptr; - sbl.clear(); - MyBots::PopulateSBL_BySpawnedBots(c, sbl); - bool cast_success = false; + sbl.remove(nullptr); - for (auto list_iter : *local_list) { - auto local_entry = list_iter->SafeCastToDepart(); + BotSpell botSpell; + botSpell.SpellId = 0; + botSpell.SpellIndex = 0; + botSpell.ManaCost = 0; - if (helper_spell_check_fail(local_entry)) { + bool isSuccess = false; + std::map> listZones; + + for (auto bot_iter : sbl) { + if (!bot_iter->IsInGroupOrRaid(tar, !single)) { continue; } - if (local_entry->single != single) { + if (bot_iter->GetBotStance() == Stance::Passive || bot_iter->GetHoldFlag() || bot_iter->GetAppearance() == eaDead || bot_iter->IsFeared() || bot_iter->IsSilenced() || bot_iter->IsAmnesiad() || bot_iter->GetHP() < 0) { continue; } - if (destination.compare(spells[local_entry->spell_id].teleport_zone)) { - continue; + std::list botSpellListItr = bot_iter->GetPrioritizedBotSpellsBySpellType(bot_iter, BotSpellTypes::Teleport, tar); + + for (std::list::iterator itr = botSpellListItr.begin(); itr != botSpellListItr.end(); ++itr) { + if (!IsValidSpell(itr->SpellId)) { + continue; + } + + if ( + (single && spells[itr->SpellId].target_type != ST_Target) || + (group && !IsGroupSpell(itr->SpellId)) || + (ae && !IsAnyAESpell(itr->SpellId)) + ) { + continue; + } + + if (list) { + auto it = listZones.find(spells[itr->SpellId].teleport_zone); + + if (it != listZones.end()) { + const auto& [val1, val2] = it->second; + + if (val1 == spells[itr->SpellId].target_type && val2 == bot_iter->GetClass()) { + continue; + } + } + + Bot::BotGroupSay( + bot_iter, + fmt::format( + "I can port you to {}.", + Saylink::Silent( + fmt::format( + "{} {} {} byname {}", + sep->arg[0], + spells[itr->SpellId].teleport_zone, + (spells[itr->SpellId].target_type == ST_Target ? "single" : (IsGroupSpell(itr->SpellId) ? "group" : "ae")), + bot_iter->GetCleanName() + ).c_str() + , ZoneLongName(ZoneID(spells[itr->SpellId].teleport_zone))) + ).c_str() + ); + + listZones.insert({ spells[itr->SpellId].teleport_zone, {spells[itr->SpellId].target_type, bot_iter->GetClass()} }); + + continue; + } + + if (destination.compare(spells[itr->SpellId].teleport_zone)) { + continue; + } + + bot_iter->SetCommandedSpell(true); + + if (!IsValidSpellAndLoS(itr->SpellId, bot_iter->HasLoS())) { + continue; + } + + if (IsInvulnerabilitySpell(itr->SpellId)) { + tar = bot_iter; //target self for invul type spells + } + + if (bot_iter->IsCommandedSpell() && bot_iter->IsCasting()) { + Bot::BotGroupSay( + bot_iter, + fmt::format( + "Interrupting {}. I have been commanded to try to cast a [{}] spell, {} on {}.", + bot_iter->CastingSpellID() ? spells[bot_iter->CastingSpellID()].name : "my spell", + bot_iter->GetSpellTypeNameByID(BotSpellTypes::Teleport), + spells[itr->SpellId].name, + tar->GetCleanName() + ).c_str() + ); + + bot_iter->InterruptSpell(); + } + + if (bot_iter->CastSpell(itr->SpellId, tar->GetID(), EQ::spells::CastingSlot::Gem2, -1, -1)) { + if (BOT_SPELL_TYPES_OTHER_BENEFICIAL(BotSpellTypes::Teleport)) { + bot_iter->SetCastedSpellType(UINT16_MAX); + } + else { + bot_iter->SetCastedSpellType(BotSpellTypes::Teleport); + } + + if (bot_iter->GetClass() != Class::Bard || RuleB(Bots, BardsAnnounceCasts)) { + Bot::BotGroupSay( + bot_iter, + fmt::format( + "Casting {} [{}] on {}.", + GetSpellName(itr->SpellId), + bot_iter->GetSpellTypeNameByID(BotSpellTypes::Teleport), + (tar == bot_iter ? "myself" : tar->GetCleanName()) + ).c_str() + ); + } + + isSuccess = true; + } + + bot_iter->SetCommandedSpell(false); + + if (isSuccess) { + return; + } + else { + 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; - } - - if (my_bot->BotPassiveCheck()) { - continue; - } - - if (!my_bot->IsInGroupOrRaid(c)) { - continue; - } - - if (local_entry->spell_id != 0 && my_bot->GetMana() < spells[local_entry->spell_id].mana) { - continue; - } - - cast_success = helper_cast_standard_spell(my_bot, target_mob, local_entry->spell_id); - - break; } - if (!cast_success) { - helper_no_available_bots(c, my_bot); + if ( + (list && listZones.empty()) || + (!list && !isSuccess) + ) { + c->Message( + Chat::Yellow, + fmt::format( + "No bots are capable of that on {}. Be sure they are in the same group, raid or raid group if necessary.", + tar ? tar->GetCleanName() : "you" + ).c_str() + ); } }