diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index e0989f4f16..d42c19b977 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -1423,6 +1423,7 @@ int bot_command_init(void) 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("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("pull", "Orders a designated bot to 'pull' an enemy", AccountStatus::Player, bot_command_pull) || @@ -5517,7 +5518,7 @@ void bot_subcommand_bot_create(Client *c, const Seperator *sep) c->Message(Chat::White, "Bot name cannot contain underscores!"); return; } - + if (arguments < 2 || !sep->IsNumber(2)) { c->Message(Chat::White, "Invalid class!"); return; @@ -10013,3 +10014,194 @@ void bot_command_caster_range(Client* c, const Seperator* sep) c->Message(Chat::White, "Incorrect argument, use help for a list of options."); } } + +void bot_command_pickpocket(Client *c, const Seperator *sep) +{ + 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_ROGUE_BIT); + Bot *my_bot = ActionableBots::AsSpawned_ByMinLevelAndClass(c, sbl, 7, 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); + if (mob_z_distance >= 25 || 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_command.h b/zone/bot_command.h index 3120380960..af608b7e0f 100644 --- a/zone/bot_command.h +++ b/zone/bot_command.h @@ -576,6 +576,7 @@ void bot_command_movement_speed(Client *c, const Seperator *sep); void bot_command_owner_option(Client *c, const Seperator *sep); void bot_command_pet(Client *c, const Seperator *sep); void bot_command_pick_lock(Client *c, const Seperator *sep); +void bot_command_pickpocket(Client* c, const Seperator* sep); void bot_command_precombat(Client* c, const Seperator* sep); void bot_command_pull(Client *c, const Seperator *sep); void bot_command_release(Client *c, const Seperator *sep);