Skip to content

Commit

Permalink
[Bots] Add Pickpocket Command (#3484)
Browse files Browse the repository at this point in the history
* Add Pickpocket Command

* Formatting

---------

Co-authored-by: Akkadius <[email protected]>
  • Loading branch information
tuday2 and Akkadius authored Jul 8, 2023
1 parent 096448d commit f06c7e8
Show file tree
Hide file tree
Showing 2 changed files with 194 additions and 1 deletion.
194 changes: 193 additions & 1 deletion zone/bot_command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) ||
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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: <enemy_target>", sep->arg[0]);
return;
}

std::list<Bot *> 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 <target> 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<std::pair<const EQ::ItemData *, uint16>> loot_selection; // <const ItemData*, charges>
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");
}
1 change: 1 addition & 0 deletions zone/bot_command.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit f06c7e8

Please sign in to comment.