Complete rework of the bot trading system (see changelog.txt)

This commit is contained in:
Uleat 2017-03-08 08:12:04 -05:00
parent f26b7a4adc
commit 09bbfbcc31
5 changed files with 416 additions and 248 deletions

View File

@ -1,5 +1,20 @@
EQEMu Changelog (Started on Sept 24, 2003 15:50)
-------------------------------------------------------
== 03/08/2017 ==
Uleat: Complete rework of the bot trading system
- Equipment slot priority can now be tailored..though, a recompile will be required
- All item validations and slot assignments for trades and returns are now performed before any actual item movements occur
- Failed trade/returned items will now go straight into the client's inventory, just like a normal trade transaction
- A 'green' message appears at the end of each successful trade informing the trader of 'accepted' and 'returned' item counts
- Bots respond to the trader directly now instead of using BotGroupSay()
- Bots will still only allow trades from their owner (currently, too high a risk of exploit and/or malicious activity)
- Partial stack movements (i.e., ammo refills) have been scoped..but, not implemented
- I have not been able to reproduce any 'illegal' weapon combinations with this code
- NOTE: The report of item duplication with bot return items appears to be an inventory desync condition
- I experienced this condition both before and after the rework with RoF+ clients (UF- appears ok)
- The bug lies within the actual client inventory system and not with bot trades
- Please post any issues with this change as they arise
== 02/27/2017 ==
Uleat: Notes on bot movement speed changes:
- Clients (players) appear to be on a different speed scale than other entities (NPCs, etc...)

View File

@ -635,18 +635,23 @@ int16 EQEmu::InventoryProfile::FindFreeSlot(bool for_bag, bool try_cursor, uint8
}
// This is a mix of HasSpaceForItem and FindFreeSlot..due to existing coding behavior, it was better to add a new helper function...
int16 EQEmu::InventoryProfile::FindFreeSlotForTradeItem(const ItemInstance* inst) {
int16 EQEmu::InventoryProfile::FindFreeSlotForTradeItem(const ItemInstance* inst, int16 general_start, int16 bag_start) {
// Do not arbitrarily use this function..it is designed for use with Client::ResetTrade() and Client::FinishTrade().
// If you have a need, use it..but, understand it is not a compatible replacement for InventoryProfile::FindFreeSlot().
// If you have a need, use it..but, understand it is not a suitable replacement for InventoryProfile::FindFreeSlot().
//
// I'll probably implement a bitmask in the new inventory system to avoid having to adjust stack bias
if ((general_start < legacy::GENERAL_BEGIN) || (general_start > legacy::GENERAL_END))
return INVALID_INDEX;
if ((bag_start < inventory::containerBegin) || (bag_start >= inventory::ContainerCount))
return INVALID_INDEX;
if (!inst || !inst->GetID())
return INVALID_INDEX;
// step 1: find room for bags (caller should really ask for slots for bags first to avoid sending them to cursor..and bag item loss)
if (inst->IsClassBag()) {
for (int16 free_slot = legacy::GENERAL_BEGIN; free_slot <= legacy::GENERAL_END; ++free_slot) {
for (int16 free_slot = general_start; free_slot <= legacy::GENERAL_END; ++free_slot) {
if (!m_inv[free_slot])
return free_slot;
}
@ -656,7 +661,7 @@ int16 EQEmu::InventoryProfile::FindFreeSlotForTradeItem(const ItemInstance* inst
// step 2: find partial room for stackables
if (inst->IsStackable()) {
for (int16 free_slot = legacy::GENERAL_BEGIN; free_slot <= legacy::GENERAL_END; ++free_slot) {
for (int16 free_slot = general_start; free_slot <= legacy::GENERAL_END; ++free_slot) {
const ItemInstance* main_inst = m_inv[free_slot];
if (!main_inst)
@ -666,14 +671,14 @@ int16 EQEmu::InventoryProfile::FindFreeSlotForTradeItem(const ItemInstance* inst
return free_slot;
}
for (int16 free_slot = legacy::GENERAL_BEGIN; free_slot <= legacy::GENERAL_END; ++free_slot) {
for (int16 free_slot = general_start; free_slot <= legacy::GENERAL_END; ++free_slot) {
const ItemInstance* main_inst = m_inv[free_slot];
if (!main_inst)
continue;
if (main_inst->IsClassBag()) { // if item-specific containers already have bad items, we won't fix it here...
for (uint8 free_bag_slot = inventory::containerBegin; (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot < inventory::ContainerCount); ++free_bag_slot) {
for (uint8 free_bag_slot = bag_start; (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot < inventory::ContainerCount); ++free_bag_slot) {
const ItemInstance* sub_inst = main_inst->GetItem(free_bag_slot);
if (!sub_inst)
@ -688,13 +693,13 @@ int16 EQEmu::InventoryProfile::FindFreeSlotForTradeItem(const ItemInstance* inst
// step 3a: find room for container-specific items (ItemClassArrow)
if (inst->GetItem()->ItemType == item::ItemTypeArrow) {
for (int16 free_slot = legacy::GENERAL_BEGIN; free_slot <= legacy::GENERAL_END; ++free_slot) {
for (int16 free_slot = general_start; free_slot <= legacy::GENERAL_END; ++free_slot) {
const ItemInstance* main_inst = m_inv[free_slot];
if (!main_inst || (main_inst->GetItem()->BagType != item::BagTypeQuiver) || !main_inst->IsClassBag())
continue;
for (uint8 free_bag_slot = inventory::containerBegin; (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot < inventory::ContainerCount); ++free_bag_slot) {
for (uint8 free_bag_slot = bag_start; (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot < inventory::ContainerCount); ++free_bag_slot) {
if (!main_inst->GetItem(free_bag_slot))
return InventoryProfile::CalcSlotId(free_slot, free_bag_slot);
}
@ -703,13 +708,13 @@ int16 EQEmu::InventoryProfile::FindFreeSlotForTradeItem(const ItemInstance* inst
// step 3b: find room for container-specific items (ItemClassSmallThrowing)
if (inst->GetItem()->ItemType == item::ItemTypeSmallThrowing) {
for (int16 free_slot = legacy::GENERAL_BEGIN; free_slot <= legacy::GENERAL_END; ++free_slot) {
for (int16 free_slot = general_start; free_slot <= legacy::GENERAL_END; ++free_slot) {
const ItemInstance* main_inst = m_inv[free_slot];
if (!main_inst || (main_inst->GetItem()->BagType != item::BagTypeBandolier) || !main_inst->IsClassBag())
continue;
for (uint8 free_bag_slot = inventory::containerBegin; (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot < inventory::ContainerCount); ++free_bag_slot) {
for (uint8 free_bag_slot = bag_start; (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot < inventory::ContainerCount); ++free_bag_slot) {
if (!main_inst->GetItem(free_bag_slot))
return InventoryProfile::CalcSlotId(free_slot, free_bag_slot);
}
@ -717,21 +722,21 @@ int16 EQEmu::InventoryProfile::FindFreeSlotForTradeItem(const ItemInstance* inst
}
// step 4: just find an empty slot
for (int16 free_slot = legacy::GENERAL_BEGIN; free_slot <= legacy::GENERAL_END; ++free_slot) {
for (int16 free_slot = general_start; free_slot <= legacy::GENERAL_END; ++free_slot) {
const ItemInstance* main_inst = m_inv[free_slot];
if (!main_inst)
return free_slot;
}
for (int16 free_slot = legacy::GENERAL_BEGIN; free_slot <= legacy::GENERAL_END; ++free_slot) {
for (int16 free_slot = general_start; free_slot <= legacy::GENERAL_END; ++free_slot) {
const ItemInstance* main_inst = m_inv[free_slot];
if (main_inst && main_inst->IsClassBag()) {
if ((main_inst->GetItem()->BagSize < inst->GetItem()->Size) || (main_inst->GetItem()->BagType == item::BagTypeBandolier) || (main_inst->GetItem()->BagType == item::BagTypeQuiver))
continue;
for (uint8 free_bag_slot = inventory::containerBegin; (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot < inventory::ContainerCount); ++free_bag_slot) {
for (uint8 free_bag_slot = bag_start; (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot < inventory::ContainerCount); ++free_bag_slot) {
if (!main_inst->GetItem(free_bag_slot))
return InventoryProfile::CalcSlotId(free_slot, free_bag_slot);
}

View File

@ -155,7 +155,7 @@ namespace EQEmu
// Locate an available inventory slot
int16 FindFreeSlot(bool for_bag, bool try_cursor, uint8 min_size = 0, bool is_arrow = false);
int16 FindFreeSlotForTradeItem(const ItemInstance* inst);
int16 FindFreeSlotForTradeItem(const ItemInstance* inst, int16 general_start = legacy::GENERAL_BEGIN, int16 bag_start = inventory::containerBegin);
// Calculate slot_id for an item within a bag
static int16 CalcSlotId(int16 slot_id); // Calc parent bag's slot_id

View File

@ -3225,14 +3225,14 @@ void Bot::BotAddEquipItem(int slot, uint32 id) {
}
// Erases the specified item from bot the NPC equipment array and from the bot inventory collection.
void Bot::BotRemoveEquipItem(int slot) {
if(slot > 0) {
uint8 materialFromSlot = EQEmu::InventoryProfile::CalcMaterialFromSlot(slot);
void Bot::BotRemoveEquipItem(int16 slot)
{
uint8 material_slot = EQEmu::InventoryProfile::CalcMaterialFromSlot(slot);
if (materialFromSlot != EQEmu::textures::materialInvalid) {
if (material_slot != EQEmu::textures::materialInvalid) {
equipment[slot] = 0; // npc has more than just material slots. Valid material should mean valid inventory index
SendWearChange(materialFromSlot);
if (materialFromSlot == EQEmu::textures::armorChest)
SendWearChange(material_slot);
if (material_slot == EQEmu::textures::armorChest)
SendWearChange(EQEmu::textures::armorArms);
}
@ -3240,7 +3240,6 @@ void Bot::BotRemoveEquipItem(int slot) {
if (UpdateActiveLight())
SendAppearancePacket(AT_Light, GetActiveLightType());
}
}
void Bot::BotTradeSwapItem(Client* client, int16 lootSlot, const EQEmu::ItemInstance* inst, const EQEmu::ItemInstance* inst_swap, uint32 equipableSlots, std::string* errorMessage, bool swap) {
@ -3326,8 +3325,15 @@ bool Bot::AddBotToGroup(Bot* bot, Group* group) {
}
// Completes a trade with a client bot owner
void Bot::FinishTrade(Client* client, BotTradeType tradeType) {
if(client && !client->GetTradeskillObject() && (client->trade->state != Trading)) {
void Bot::FinishTrade(Client* client, BotTradeType tradeType)
{
if (!client || (GetOwner() != client) || client->GetTradeskillObject() || client->trade->state == Trading) {
if (client)
client->ResetTrade();
return;
}
// these notes are not correct or obselete
if (tradeType == BotTradeClientNormal) {
// Items being traded are found in the normal trade window used to trade between a Client and a Client or NPC
// Items in this mode are found in slot ids 3000 thru 3003 - thought bots used the full 8-slot window..?
@ -3342,227 +3348,369 @@ void Bot::FinishTrade(Client* client, BotTradeType tradeType) {
// PerformTradeWithClient(331, 340, client);
}
}
}
// Perfoms the actual trade action with a client bot owner
void Bot::PerformTradeWithClient(int16 beginSlotID, int16 endSlotID, Client* client) {
if(client) {
// TODO: Figure out what the actual max slot id is
const int MAX_SLOT_ID = EQEmu::legacy::TRADE_BAGS_END; // was the old incorrect 3179..
uint32 items[MAX_SLOT_ID] = {0};
uint8 charges[MAX_SLOT_ID] = {0};
bool botCanWear[MAX_SLOT_ID] = {0};
for(int16 i = beginSlotID; i <= endSlotID; ++i) {
bool BotCanWear = false;
bool UpdateClient = false;
bool already_returned = false;
EQEmu::InventoryProfile& clientInventory = client->GetInv();
const EQEmu::ItemInstance* inst = clientInventory[i];
if(inst) {
items[i] = inst->GetItem()->ID;
charges[i] = inst->GetCharges();
}
if (i == EQEmu::inventory::slotCursor)
UpdateClient = true;
//EQoffline: will give the items to the bots and change the bot stats
if(inst && (GetBotOwner() == client->CastToMob()) && !IsEngaged()) {
std::string TempErrorMessage;
const EQEmu::ItemData* mWeaponItem = inst->GetItem();
bool failedLoreCheck = false;
for (int m = EQEmu::inventory::socketBegin; m < EQEmu::inventory::SocketCount; ++m) {
EQEmu::ItemInstance *itm = inst->GetAugment(m);
if(itm)
void Bot::PerformTradeWithClient(int16 beginSlotID, int16 endSlotID, Client* client)
{
if(CheckLoreConflict(itm->GetItem())) {
failedLoreCheck = true;
}
}
}
if(CheckLoreConflict(mWeaponItem)) {
failedLoreCheck = true;
}
if(failedLoreCheck) {
Message_StringID(0, DUP_LORE);
}
if(!failedLoreCheck && mWeaponItem && inst->IsEquipable(GetBaseRace(), GetClass()) && (GetLevel() >= mWeaponItem->ReqLevel)) {
BotCanWear = true;
botCanWear[i] = BotCanWear;
EQEmu::ItemInstance* swap_item = nullptr;
using namespace EQEmu;
const char* equipped[EQEmu::legacy::EQUIPMENT_SIZE + 1] = { "Charm", "Left Ear", "Head", "Face", "Right Ear", "Neck", "Shoulders", "Arms", "Back",
"Left Wrist", "Right Wrist", "Range", "Hands", "Primary Hand", "Secondary Hand",
"Left Finger", "Right Finger", "Chest", "Legs", "Feet", "Waist", "Ammo", "Powersource" };
bool success = false;
int how_many_slots = 0;
for (int j = EQEmu::legacy::EQUIPMENT_BEGIN; j <= (EQEmu::legacy::EQUIPMENT_END + 1); ++j) {
if((mWeaponItem->Slots & (1 << j))) {
if (j == 22)
j = 9999;
struct ClientTrade {
const ItemInstance* tradeItemInstance;
int16 fromClientSlot;
int16 toBotSlot;
int adjustStackSize;
how_many_slots++;
if(!GetBotItem(j)) {
if (j == EQEmu::inventory::slotPrimary) {
if (mWeaponItem->IsType2HWeapon()) {
if (GetBotItem(EQEmu::inventory::slotSecondary)) {
if (mWeaponItem && mWeaponItem->IsType2HWeapon()) {
if (client->CheckLoreConflict(GetBotItem(EQEmu::inventory::slotSecondary)->GetItem())) {
failedLoreCheck = true;
}
}
else {
EQEmu::ItemInstance* remove_item = GetBotItem(EQEmu::inventory::slotSecondary);
BotTradeSwapItem(client, EQEmu::inventory::slotSecondary, 0, remove_item, remove_item->GetItem()->Slots, &TempErrorMessage, false);
}
}
}
if(!failedLoreCheck) {
BotTradeAddItem(mWeaponItem->ID, inst, inst->GetCharges(), mWeaponItem->Slots, j, &TempErrorMessage);
success = true;
}
break;
}
else if (j == EQEmu::inventory::slotSecondary) {
if(inst->IsWeapon()) {
if(CanThisClassDualWield()) {
BotTradeAddItem(mWeaponItem->ID, inst, inst->GetCharges(), mWeaponItem->Slots, j, &TempErrorMessage);
success = true;
}
else {
BotGroupSay(this, "I can't Dual Wield yet.");
--how_many_slots;
}
}
else {
BotTradeAddItem(mWeaponItem->ID, inst, inst->GetCharges(), mWeaponItem->Slots, j, &TempErrorMessage);
success = true;
}
if(success) {
if (GetBotItem(EQEmu::inventory::slotPrimary)) {
EQEmu::ItemInstance* remove_item = GetBotItem(EQEmu::inventory::slotPrimary);
if (remove_item->GetItem()->IsType2HWeapon()) {
BotTradeSwapItem(client, EQEmu::inventory::slotPrimary, 0, remove_item, remove_item->GetItem()->Slots, &TempErrorMessage, false);
}
}
break;
}
}
else {
BotTradeAddItem(mWeaponItem->ID, inst, inst->GetCharges(), mWeaponItem->Slots, j, &TempErrorMessage);
success = true;
break;
}
}
}
}
if(!success) {
for (int j = EQEmu::legacy::EQUIPMENT_BEGIN; j <= (EQEmu::legacy::EQUIPMENT_END + 1); ++j) {
if((mWeaponItem->Slots & (1 << j))) {
if (j == 22)
j = 9999;
ClientTrade(const ItemInstance* item, int16 from) : tradeItemInstance(item), fromClientSlot(from), toBotSlot(legacy::SLOT_INVALID), adjustStackSize(0) { }
};
swap_item = GetBotItem(j);
failedLoreCheck = false;
for (int k = EQEmu::inventory::socketBegin; k < EQEmu::inventory::SocketCount; ++k) {
EQEmu::ItemInstance *itm = swap_item->GetAugment(k);
if(itm)
{
if(client->CheckLoreConflict(itm->GetItem())) {
failedLoreCheck = true;
struct ClientReturn {
const ItemInstance* returnItemInstance;
int16 fromBotSlot;
int16 toClientSlot;
int adjustStackSize;
ClientReturn(const ItemInstance* item, int16 from) : returnItemInstance(item), fromBotSlot(from), toClientSlot(legacy::SLOT_INVALID), adjustStackSize(0) { }
};
static const int16 proxyPowerSource = 22;
static const int16 bot_equip_order[(legacy::EQUIPMENT_SIZE + 1)] = {
inventory::slotCharm, inventory::slotEar1, inventory::slotHead, inventory::slotFace,
inventory::slotEar2, inventory::slotNeck, inventory::slotShoulders, inventory::slotArms,
inventory::slotBack, inventory::slotWrist1, inventory::slotWrist2, inventory::slotRange,
inventory::slotHands, inventory::slotPrimary, inventory::slotSecondary, inventory::slotFinger1,
inventory::slotFinger2, inventory::slotChest, inventory::slotLegs, inventory::slotFeet,
inventory::slotWaist, inventory::slotAmmo, proxyPowerSource // inventory::slotPowerSource
};
enum { stageStackable = 0, stageEmpty, stageReplaceable };
if (!client) {
Emote("NO CLIENT");
return;
}
if (client != GetOwner()) {
client->Message(CC_Red, "You are not the owner of this bot - Trade Canceled.");
client->ResetTrade();
return;
}
if ((beginSlotID != legacy::TRADE_BEGIN) && (beginSlotID != inventory::slotCursor)) {
client->Message(CC_Red, "Trade request processing from illegal 'begin' slot - Trade Canceled.");
client->ResetTrade();
return;
}
if(client->CheckLoreConflict(swap_item->GetItem())) {
failedLoreCheck = true;
if ((endSlotID != legacy::TRADE_END) && (endSlotID != inventory::slotCursor)) {
client->Message(CC_Red, "Trade request processing from illegal 'end' slot - Trade Canceled.");
client->ResetTrade();
return;
}
if(!failedLoreCheck) {
if (j == EQEmu::inventory::slotPrimary) {
if (mWeaponItem->IsType2HWeapon()) {
if (GetBotItem(EQEmu::inventory::slotSecondary)) {
if (client->CheckLoreConflict(GetBotItem(EQEmu::inventory::slotSecondary)->GetItem())) {
failedLoreCheck = true;
if (((beginSlotID == inventory::slotCursor) && (endSlotID != inventory::slotCursor)) || ((beginSlotID != inventory::slotCursor) && (endSlotID == inventory::slotCursor))) {
client->Message(CC_Red, "Trade request processing illegal slot range - Trade Canceled.");
client->ResetTrade();
return;
}
else {
EQEmu::ItemInstance* remove_item = GetBotItem(EQEmu::inventory::slotSecondary);
BotTradeSwapItem(client, EQEmu::inventory::slotSecondary, 0, remove_item, remove_item->GetItem()->Slots, &TempErrorMessage, false);
if (endSlotID < beginSlotID) {
client->Message(CC_Red, "Trade request processing in reverse slot order - Trade Canceled.");
client->ResetTrade();
return;
}
if (client->IsEngaged() || IsEngaged()) {
client->Message(CC_Yellow, "You may not perform a trade while engaged - Trade Canceled!");
client->ResetTrade();
return;
}
std::list<ClientTrade> client_trade;
std::list<ClientReturn> client_return;
// pre-checks for incoming illegal transfers
for (int16 trade_index = beginSlotID; trade_index <= endSlotID; ++trade_index) {
auto trade_instance = client->GetInv()[trade_index];
if (!trade_instance)
continue;
if (!trade_instance->GetItem()) {
// TODO: add logging
client->Message(CC_Red, "A server error was encountered while processing client slot %i - Trade Canceled.", trade_index);
client->ResetTrade();
return;
}
if(!failedLoreCheck) {
BotTradeSwapItem(client, EQEmu::inventory::slotPrimary, inst, swap_item, mWeaponItem->Slots, &TempErrorMessage);
success = true;
if ((trade_index != inventory::slotCursor) && !trade_instance->IsDroppable()) {
// TODO: add logging
client->Message(CC_Red, "Trade hack detected - Trade Canceled.");
client->ResetTrade();
return;
}
break;
if (CheckLoreConflict(trade_instance->GetItem())) {
client->Message(CC_Yellow, "This bot already has lore equipment matching the item '%s' - Trade Canceled!", trade_instance->GetItem()->Name);
client->ResetTrade();
return;
}
else if (j == EQEmu::inventory::slotSecondary) {
if(inst->IsWeapon()) {
if(CanThisClassDualWield()) {
BotTradeSwapItem(client, EQEmu::inventory::slotSecondary, inst, swap_item, mWeaponItem->Slots, &TempErrorMessage);
success = true;
if (!trade_instance->IsType(item::ItemClassCommon)) {
client_return.push_back(ClientReturn(trade_instance, trade_index));
continue;
}
else {
botCanWear[i] = false;
BotGroupSay(this, "I can't Dual Wield yet.");
if (!trade_instance->IsEquipable(GetBaseRace(), GetClass()) || (GetLevel() < trade_instance->GetItem()->ReqLevel)) {
client_return.push_back(ClientReturn(trade_instance, trade_index));
continue;
}
client_trade.push_back(ClientTrade(trade_instance, trade_index));
}
else {
BotTradeSwapItem(client, EQEmu::inventory::slotSecondary, inst, swap_item, mWeaponItem->Slots, &TempErrorMessage);
success = true;
// check for incoming lore hacks
for (auto& trade_iterator : client_trade) {
if (!trade_iterator.tradeItemInstance->GetItem()->LoreFlag)
continue;
for (const auto& check_iterator : client_trade) {
if (check_iterator.fromClientSlot == trade_iterator.fromClientSlot)
continue;
if (!check_iterator.tradeItemInstance->GetItem()->LoreFlag)
continue;
if ((trade_iterator.tradeItemInstance->GetItem()->LoreGroup == -1) && (check_iterator.tradeItemInstance->GetItem()->ID == trade_iterator.tradeItemInstance->GetItem()->ID)) {
// TODO: add logging
client->Message(CC_Red, "Trade hack detected - Trade Canceled.");
client->ResetTrade();
return;
}
if (success && GetBotItem(EQEmu::inventory::slotPrimary)) {
EQEmu::ItemInstance* remove_item = GetBotItem(EQEmu::inventory::slotPrimary);
if (remove_item->GetItem()->IsType2HWeapon()) {
BotTradeSwapItem(client, EQEmu::inventory::slotPrimary, 0, remove_item, remove_item->GetItem()->Slots, &TempErrorMessage, false);
}
}
break;
}
else {
BotTradeSwapItem(client, j, inst, swap_item, mWeaponItem->Slots, &TempErrorMessage);
success = true;
break;
}
}
else {
botCanWear[i] = false;
Message_StringID(0, PICK_LORE);
break;
}
}
}
}
if(success) {
if(how_many_slots > 1) {
Message(300, "If you want this item in a different slot, use #bot inventory remove <slot_id> to clear the spot.");
}
CalcBotStats();
}
}
}
if(inst) {
client->DeleteItemInInventory(i, 0, UpdateClient);
if(!botCanWear[i]) {
client->PushItemOnCursor(*inst, true);
if ((trade_iterator.tradeItemInstance->GetItem()->LoreGroup > 0) && (check_iterator.tradeItemInstance->GetItem()->LoreGroup == trade_iterator.tradeItemInstance->GetItem()->LoreGroup)) {
// TODO: add logging
client->Message(CC_Red, "Trade hack detected - Trade Canceled.");
client->ResetTrade();
return;
}
}
}
const EQEmu::ItemData* item2 = 0;
for(int y = beginSlotID; y <= endSlotID; ++y) {
item2 = database.GetItem(items[y]);
if(item2) {
if(botCanWear[y]) {
BotGroupSay(this, "Thank you for the %s, %s!", item2->Name, client->GetName());
// find equipment slots
const bool can_dual_wield = CanThisClassDualWield();
bool melee_2h_weapon = false;
bool melee_secondary = false;
//for (unsigned stage_loop = stageStackable; stage_loop <= stageReplaceable; ++stage_loop) { // awaiting implementation
for (unsigned stage_loop = stageEmpty; stage_loop <= stageReplaceable; ++stage_loop) {
for (auto& trade_iterator : client_trade) {
if (trade_iterator.toBotSlot != legacy::SLOT_INVALID)
continue;
auto trade_instance = trade_iterator.tradeItemInstance;
/*if ((stage_loop == stageStackable) && !trade_instance->IsStackable())
continue;*/
for (auto index : bot_equip_order) {
if (!(trade_instance->GetItem()->Slots & (1 << index)))
continue;
//if (stage_loop == stageStackable) {
// // TODO: implement
// continue;
//}
if (stage_loop != stageReplaceable) {
if ((index == proxyPowerSource) && m_inv[inventory::slotPowerSource])
continue;
else if (m_inv[index])
continue;
}
bool slot_taken = false;
for (const auto& check_iterator : client_trade) {
if (check_iterator.fromClientSlot == trade_iterator.fromClientSlot)
continue;
if (check_iterator.toBotSlot == index) {
slot_taken = true;
break;
}
}
if (slot_taken)
continue;
if (index == inventory::slotPrimary) {
if (trade_instance->GetItem()->IsType2HWeapon()) {
if (!melee_secondary) {
melee_2h_weapon = true;
if (m_inv[inventory::slotSecondary])
client_return.push_back(ClientReturn(m_inv[inventory::slotSecondary], inventory::slotSecondary));
}
else {
BotGroupSay(this, "I can't use this %s!", item2->Name);
continue;
}
}
}
if (index == inventory::slotSecondary) {
if (!melee_2h_weapon) {
if ((can_dual_wield && trade_instance->GetItem()->IsType1HWeapon()) || trade_instance->GetItem()->IsTypeShield() || !trade_instance->IsWeapon())
melee_secondary = true;
else
continue;
}
else {
continue;
}
}
if (index == proxyPowerSource) {
trade_iterator.toBotSlot = inventory::slotPowerSource;
if (m_inv[inventory::slotPowerSource])
client_return.push_back(ClientReturn(m_inv[inventory::slotPowerSource], inventory::slotPowerSource));
}
else {
trade_iterator.toBotSlot = index;
if (m_inv[index])
client_return.push_back(ClientReturn(m_inv[index], index));
}
break;
}
}
}
// move unassignable items from trade list to return list
for (std::list<ClientTrade>::iterator trade_iterator = client_trade.begin(); trade_iterator != client_trade.end();) {
if (trade_iterator->toBotSlot == legacy::SLOT_INVALID) {
client_return.push_back(ClientReturn(trade_iterator->tradeItemInstance, trade_iterator->fromClientSlot));
trade_iterator = client_trade.erase(trade_iterator);
continue;
}
++trade_iterator;
}
// out-going return checks for client
for (auto& return_iterator : client_return) {
auto return_instance = return_iterator.returnItemInstance;
if (!return_instance)
continue;
if (!return_instance->GetItem()) {
// TODO: add logging
client->Message(CC_Red, "A server error was encountered while processing bot slot %i - Trade Canceled.", return_iterator.fromBotSlot);
client->ResetTrade();
return;
}
if (client->CheckLoreConflict(return_instance->GetItem())) {
client->Message(CC_Yellow, "You already have lore equipment matching the item '%s' - Trade Canceled!", return_instance->GetItem()->Name);
client->ResetTrade();
return;
}
if (return_iterator.fromBotSlot == inventory::slotCursor) {
return_iterator.toClientSlot = inventory::slotCursor;
}
else {
int16 client_search_general = legacy::GENERAL_BEGIN;
int16 client_search_bag = inventory::containerBegin;
bool run_search = true;
while (run_search) {
int16 client_test_slot = client->GetInv().FindFreeSlotForTradeItem(return_instance, client_search_general, client_search_bag);
if (client_test_slot == legacy::SLOT_INVALID) {
run_search = false;
continue;
}
bool slot_taken = false;
for (const auto& check_iterator : client_return) {
if (check_iterator.fromBotSlot == return_iterator.fromBotSlot)
continue;
if ((check_iterator.toClientSlot == client_test_slot) && (client_test_slot != inventory::slotCursor)) {
slot_taken = true;
break;
}
}
if (slot_taken) {
client_search_general = InventoryProfile::CalcSlotId(client_test_slot);
client_search_bag = InventoryProfile::CalcBagIdx(client_test_slot);
++client_search_bag;
if (client_search_bag >= inventory::ContainerCount) {
client_search_bag = inventory::containerBegin;
// incrementing this past legacy::GENERAL_END triggers the (client_test_slot == legacy::SLOT_INVALID) at the beginning of the search loop
// ideally, this will never occur because we always start fresh with each loop iteration and should receive SLOT_CURSOR as a return value
++client_search_general;
}
continue;
}
return_iterator.toClientSlot = client_test_slot;
run_search = false;
}
}
if (return_iterator.toClientSlot == legacy::SLOT_INVALID) {
client->Message(CC_Yellow, "You do not have room to complete this trade - Trade Canceled!");
client->ResetTrade();
return;
}
}
// perform actual trades
// returns first since clients have trade slots and bots do not
for (auto& return_iterator : client_return) {
// TODO: code for stackables
if (return_iterator.fromBotSlot == inventory::slotCursor) { // failed trade return
// no movement action required
}
else if ((return_iterator.fromBotSlot >= legacy::TRADE_BEGIN) && (return_iterator.fromBotSlot <= legacy::TRADE_END)) { // failed trade returns
client->PutItemInInventory(return_iterator.toClientSlot, *return_iterator.returnItemInstance);
client->SendItemPacket(return_iterator.toClientSlot, return_iterator.returnItemInstance, ItemPacketTrade);
client->DeleteItemInInventory(return_iterator.fromBotSlot);
}
else { // successful trade returns
auto return_instance = m_inv.PopItem(return_iterator.fromBotSlot);
if (*return_instance != *return_iterator.returnItemInstance) {
// TODO: add logging
}
if (!botdb.DeleteItemBySlot(GetBotID(), return_iterator.fromBotSlot))
client->Message(CC_Red, "%s (slot: %i, name: '%s')", BotDatabase::fail::DeleteItemBySlot(), return_iterator.fromBotSlot, (return_instance ? return_instance->GetItem()->Name : "nullptr"));
BotRemoveEquipItem(return_iterator.fromBotSlot);
client->PutItemInInventory(return_iterator.toClientSlot, *return_instance, true);
InventoryProfile::MarkDirty(return_instance);
}
return_iterator.returnItemInstance = nullptr;
}
// trades can now go in as empty slot inserts
for (auto& trade_iterator : client_trade) {
// TODO: code for stackables
if (!botdb.SaveItemBySlot(this, trade_iterator.toBotSlot, trade_iterator.tradeItemInstance))
client->Message(CC_Red, "%s (slot: %i, name: '%s')", BotDatabase::fail::SaveItemBySlot(), trade_iterator.toBotSlot, (trade_iterator.tradeItemInstance ? trade_iterator.tradeItemInstance->GetItem()->Name : "nullptr"));
m_inv.PutItem(trade_iterator.toBotSlot, *trade_iterator.tradeItemInstance);
this->BotAddEquipItem(trade_iterator.toBotSlot, (trade_iterator.tradeItemInstance ? trade_iterator.tradeItemInstance->GetID() : 0));
client->DeleteItemInInventory(trade_iterator.fromClientSlot, 0, true);
trade_iterator.tradeItemInstance = nullptr;
}
// trade announcements
for (const auto& return_iterator : client_return) { // item pointers should be nulled by this point
if (((return_iterator.fromBotSlot < legacy::TRADE_BEGIN) || (return_iterator.fromBotSlot > legacy::TRADE_END)) && (return_iterator.fromBotSlot != inventory::slotCursor))
continue;
if (client->GetInv()[return_iterator.toClientSlot])
client->Message(MT_Tell, "%s tells you, \"%s, I can't use this '%s.'\"", GetCleanName(), client->GetName(), client->GetInv()[return_iterator.toClientSlot]->GetItem()->Name);
}
for (const auto& trade_iterator : client_trade) { // item pointers should be nulled by this point
if (m_inv[trade_iterator.toBotSlot])
client->Message(MT_Tell, "%s tells you, \"Thank you for the '%s,' %s!\"", GetCleanName(), m_inv[trade_iterator.toBotSlot]->GetItem()->Name, client->GetName());
}
size_t accepted_count = client_trade.size();
size_t returned_count = client_return.size();
client->Message(CC_Lime, "Trade with '%s' resulted in %i accepted item%s, %i returned item%s.", GetCleanName(), accepted_count, ((accepted_count == 1) ? "" : "s"), returned_count, ((returned_count == 1) ? "" : "s"));
}
bool Bot::Death(Mob *killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill) {

View File

@ -655,7 +655,7 @@ public:
// Publicized private functions
static NPCType FillNPCTypeStruct(uint32 botSpellsID, std::string botName, std::string botLastName, uint8 botLevel, uint16 botRace, uint8 botClass, uint8 gender, float size, uint32 face, uint32 hairStyle, uint32 hairColor, uint32 eyeColor, uint32 eyeColor2, uint32 beardColor, uint32 beard, uint32 drakkinHeritage, uint32 drakkinTattoo, uint32 drakkinDetails, int32 hp, int32 mana, int32 mr, int32 cr, int32 dr, int32 fr, int32 pr, int32 corrup, int32 ac, uint32 str, uint32 sta, uint32 dex, uint32 agi, uint32 _int, uint32 wis, uint32 cha, uint32 attack);
void BotRemoveEquipItem(int slot);
void BotRemoveEquipItem(int16 slot);
void RemoveBotItemBySlot(uint32 slotID, std::string* errorMessage);
uint32 GetTotalPlayTime();