eqemu-server/zone/inventory.cpp
akkadius a5020a68f0 Implemented a Stop_Return feature (Accidental item handin prevention) that will be symmetrically used with plugin::return_items that I am currently running live testing on EZ before releasing to EQEmu. This does not hurt to have this in the source.
Fixed crash where 'attacker' validation is not being checked
Removed petition console spam that does not follow traditional logging and is useless
Made fix with SympatheticProcChances where it was checking for TempItem->Focus.Effect instead of TempItemAug->Focus.Effect
2014-08-19 10:55:29 -05:00

2686 lines
93 KiB
C++

/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2003 EQEMu Development Team (http://eqemulator.net)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../common/debug.h"
#include "masterentity.h"
#include "worldserver.h"
#include "net.h"
#include "zonedb.h"
#include "../common/spdat.h"
#include "../common/packet_dump.h"
#include "../common/packet_functions.h"
#include "petitions.h"
#include "../common/serverinfo.h"
#include "../common/ZoneNumbers.h"
#include "../common/moremath.h"
#include "../common/guilds.h"
#include "../common/logsys.h"
#include "../common/StringUtil.h"
#include "StringIDs.h"
#include "NpcAI.h"
#include "QuestParserCollection.h"
extern WorldServer worldserver;
// @merth: this needs to be touched up
uint32 Client::NukeItem(uint32 itemnum, uint8 where_to_check) {
if (itemnum == 0)
return 0;
uint32 x = 0;
ItemInst *cur = nullptr;
int i;
if(where_to_check & invWhereWorn) {
for (i = EmuConstants::EQUIPMENT_BEGIN; i <= EmuConstants::EQUIPMENT_END; i++) {
if (GetItemIDAt(i) == itemnum || (itemnum == 0xFFFE && GetItemIDAt(i) != INVALID_ID)) {
cur = m_inv.GetItem(i);
if(cur && cur->GetItem()->Stackable) {
x += cur->GetCharges();
} else {
x++;
}
DeleteItemInInventory(i, 0, true);
}
}
if (GetItemIDAt(MainPowerSource) == itemnum || (itemnum == 0xFFFE && GetItemIDAt(MainPowerSource) != INVALID_ID)) {
cur = m_inv.GetItem(MainPowerSource);
if(cur && cur->GetItem()->Stackable) {
x += cur->GetCharges();
} else {
x++;
}
if (GetClientVersion() >= EQClientSoF)
DeleteItemInInventory(MainPowerSource, 0, true);
else
DeleteItemInInventory(MainPowerSource, 0, false); // Prevents Titanium crash
}
}
if(where_to_check & invWhereCursor) {
if (GetItemIDAt(MainCursor) == itemnum || (itemnum == 0xFFFE && GetItemIDAt(MainCursor) != INVALID_ID)) {
cur = m_inv.GetItem(MainCursor);
if(cur && cur->GetItem()->Stackable) {
x += cur->GetCharges();
} else {
x++;
}
DeleteItemInInventory(MainCursor, 0, true);
}
for (i = EmuConstants::CURSOR_BAG_BEGIN; i <= EmuConstants::CURSOR_BAG_END; i++) {
if (GetItemIDAt(i) == itemnum || (itemnum == 0xFFFE && GetItemIDAt(i) != INVALID_ID)) {
cur = m_inv.GetItem(i);
if(cur && cur->GetItem()->Stackable) {
x += cur->GetCharges();
} else {
x++;
}
DeleteItemInInventory(i, 0, true);
}
}
}
if(where_to_check & invWherePersonal) {
for (i = EmuConstants::GENERAL_BEGIN; i <= EmuConstants::GENERAL_END; i++) {
if (GetItemIDAt(i) == itemnum || (itemnum == 0xFFFE && GetItemIDAt(i) != INVALID_ID)) {
cur = m_inv.GetItem(i);
if(cur && cur->GetItem()->Stackable) {
x += cur->GetCharges();
} else {
x++;
}
DeleteItemInInventory(i, 0, true);
}
}
for (i = EmuConstants::GENERAL_BAGS_BEGIN; i <= EmuConstants::GENERAL_BAGS_END; i++) {
if (GetItemIDAt(i) == itemnum || (itemnum == 0xFFFE && GetItemIDAt(i) != INVALID_ID)) {
cur = m_inv.GetItem(i);
if(cur && cur->GetItem()->Stackable) {
x += cur->GetCharges();
} else {
x++;
}
DeleteItemInInventory(i, 0, true);
}
}
}
if(where_to_check & invWhereBank) {
for (i = EmuConstants::BANK_BEGIN; i <= EmuConstants::BANK_END; i++) {
if (GetItemIDAt(i) == itemnum || (itemnum == 0xFFFE && GetItemIDAt(i) != INVALID_ID)) {
cur = m_inv.GetItem(i);
if(cur && cur->GetItem()->Stackable) {
x += cur->GetCharges();
} else {
x++;
}
DeleteItemInInventory(i, 0, true);
}
}
for (i = EmuConstants::BANK_BAGS_BEGIN; i <= EmuConstants::BANK_BAGS_END; i++) {
if (GetItemIDAt(i) == itemnum || (itemnum == 0xFFFE && GetItemIDAt(i) != INVALID_ID)) {
cur = m_inv.GetItem(i);
if(cur && cur->GetItem()->Stackable) {
x += cur->GetCharges();
} else {
x++;
}
DeleteItemInInventory(i, 0, true);
}
}
}
if(where_to_check & invWhereSharedBank) {
for (i = EmuConstants::SHARED_BANK_BEGIN; i <= EmuConstants::SHARED_BANK_END; i++) {
if (GetItemIDAt(i) == itemnum || (itemnum == 0xFFFE && GetItemIDAt(i) != INVALID_ID)) {
cur = m_inv.GetItem(i);
if(cur && cur->GetItem()->Stackable) {
x += cur->GetCharges();
} else {
x++;
}
DeleteItemInInventory(i, 0, true);
}
}
for (i = EmuConstants::SHARED_BANK_BAGS_BEGIN; i <= EmuConstants::SHARED_BANK_BAGS_END; i++) {
if (GetItemIDAt(i) == itemnum || (itemnum == 0xFFFE && GetItemIDAt(i) != INVALID_ID)) {
cur = m_inv.GetItem(i);
if(cur && cur->GetItem()->Stackable) {
x += cur->GetCharges();
} else {
x++;
}
DeleteItemInInventory(i, 0, true);
}
}
}
return x;
}
bool Client::CheckLoreConflict(const Item_Struct* item) {
if (!item)
return false;
if (!(item->LoreFlag))
return false;
if (item->LoreGroup == -1) // Standard lore items; look everywhere except the shared bank, return the result
return (m_inv.HasItem(item->ID, 0, ~invWhereSharedBank) != INVALID_INDEX);
//If the item has a lore group, we check for other items with the same group and return the result
return (m_inv.HasItemByLoreGroup(item->LoreGroup, ~invWhereSharedBank) != INVALID_INDEX);
}
bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, uint32 aug3, uint32 aug4, uint32 aug5, bool attuned, uint16 to_slot) {
/* Set a timestamp in an entity variable for plugin check_handin.pl in return_items
This will stopgap players from items being returned if global_npc.pl has a catch all return_items
*/
struct timeval read_time;
char buffer[50];
gettimeofday(&read_time, 0);
sprintf(buffer, "%i.%i \n", read_time.tv_sec, read_time.tv_usec);
this->SetEntityVariable("Recieved_Item", buffer);
// TODO: update calling methods and script apis to handle a failure return
const Item_Struct* item = database.GetItem(item_id);
// make sure the item exists
if(item == nullptr) {
Message(13, "Item %u does not exist.", item_id);
mlog(INVENTORY__ERROR, "Player %s on account %s attempted to create an item with an invalid id.\n(Item: %u, Aug1: %u, Aug2: %u, Aug3: %u, Aug4: %u, Aug5: %u)\n",
GetName(), account_name, item_id, aug1, aug2, aug3, aug4, aug5);
return false;
}
// check that there is not a lore conflict between base item and existing inventory
else if(CheckLoreConflict(item)) {
// DuplicateLoreMessage(item_id);
Message(13, "You already have a lore %s (%i) in your inventory.", item->Name, item_id);
return false;
}
// check to make sure we are augmenting an augmentable item
else if(((item->ItemClass != ItemClassCommon) || (item->AugType > 0)) && (aug1 | aug2 | aug3 | aug4 | aug5)) {
Message(13, "You can not augment an augment or a non-common class item.");
mlog(INVENTORY__ERROR, "Player %s on account %s attempted to augment an augment or a non-common class item.\n(Item: %u, Aug1: %u, Aug2: %u, Aug3: %u, Aug4: %u, Aug5: %u)\n",
GetName(), account_name, item->ID, aug1, aug2, aug3, aug4, aug5);
return false;
}
// This code is ready to implement once the item load code is changed to process the 'minstatus' field.
// Checking #iteminfo in-game verfies that item->MinStatus is set to '0' regardless of field value.
// An optional sql script will also need to be added, once this goes live, to allow changing of the min status.
// check to make sure we are a GM if the item is GM-only
/*
else if(item->MinStatus && ((this->Admin() < item->MinStatus) || (this->Admin() < RuleI(GM, MinStatusToSummonItem)))) {
Message(13, "You are not a GM or do not have the status to summon this item.");
mlog(INVENTORY__ERROR, "Player %s on account %s attempted to create a GM-only item with a status of %i.\n(Item: %u, Aug1: %u, Aug2: %u, Aug3: %u, Aug4: %u, Aug5: %u, MinStatus: %u)\n",
GetName(), account_name, this->Admin(), item->ID, aug1, aug2, aug3, aug4, aug5, item->MinStatus);
return false;
}
*/
uint32 augments[EmuConstants::ITEM_COMMON_SIZE] = { aug1, aug2, aug3, aug4, aug5 };
uint32 classes = item->Classes;
uint32 races = item->Races;
uint32 slots = item->Slots;
bool enforcewear = RuleB(Inventory, EnforceAugmentWear);
bool enforcerestr = RuleB(Inventory, EnforceAugmentRestriction);
bool enforceusable = RuleB(Inventory, EnforceAugmentUsability);
for (int iter = AUG_BEGIN; iter < EmuConstants::ITEM_COMMON_SIZE; ++iter) {
const Item_Struct* augtest = database.GetItem(augments[iter]);
if(augtest == nullptr) {
if(augments[iter]) {
Message(13, "Augment %u (Aug%i) does not exist.", augments[iter], iter + 1);
mlog(INVENTORY__ERROR, "Player %s on account %s attempted to create an augment (Aug%i) with an invalid id.\n(Item: %u, Aug1: %u, Aug2: %u, Aug3: %u, Aug4: %u, Aug5: %u)\n",
GetName(), account_name, (iter + 1), item->ID, aug1, aug2, aug3, aug4, aug5);
return false;
}
}
else {
// check that there is not a lore conflict between augment and existing inventory
if(CheckLoreConflict(augtest)) {
// DuplicateLoreMessage(augtest->ID);
Message(13, "You already have a lore %s (%u) in your inventory.", augtest->Name, augtest->ID);
return false;
}
// check that augment is an actual augment
else if(augtest->AugType == 0) {
Message(13, "%s (%u) (Aug%i) is not an actual augment.", augtest->Name, augtest->ID, iter + 1);
mlog(INVENTORY__ERROR, "Player %s on account %s attempted to use a non-augment item (Aug%i) as an augment.\n(Item: %u, Aug1: %u, Aug2: %u, Aug3: %u, Aug4: %u, Aug5: %u)\n",
GetName(), account_name, item->ID, (iter + 1), aug1, aug2, aug3, aug4, aug5);
return false;
}
// Same as GM check above
// check to make sure we are a GM if the augment is GM-only
/*
else if(augtest->MinStatus && ((this->Admin() < augtest->MinStatus) || (this->Admin() < RuleI(GM, MinStatusToSummonItem)))) {
Message(13, "You are not a GM or do not have the status to summon this augment.");
mlog(INVENTORY__ERROR, "Player %s on account %s attempted to create a GM-only augment (Aug%i) with a status of %i.\n(Item: %u, Aug1: %u, Aug2: %u, Aug3: %u, Aug4: %u, Aug5: %u, MinStatus: %u)\n",
GetName(), account_name, (iter + 1), this->Admin(), item->ID, aug1, aug2, aug3, aug4, aug5, item->MinStatus);
return false;
}
*/
// check for augment type allowance
if(enforcewear) {
if((item->AugSlotType[iter] == AugTypeNone) || !(((uint32)1 << (item->AugSlotType[iter] - 1)) & augtest->AugType)) {
Message(13, "Augment %u (Aug%i) is not acceptable wear on Item %u.", augments[iter], iter + 1, item->ID);
mlog(INVENTORY__ERROR, "Player %s on account %s attempted to augment an item with an unacceptable augment type (Aug%i).\n(Item: %u, Aug1: %u, Aug2: %u, Aug3: %u, Aug4: %u, Aug5: %u)\n",
GetName(), account_name, (iter + 1), item->ID, aug1, aug2, aug3, aug4, aug5);
return false;
}
if(item->AugSlotVisible[iter] == 0) {
Message(13, "Item %u has not evolved enough to accept Augment %u (Aug%i).", item->ID, augments[iter], iter + 1);
mlog(INVENTORY__ERROR, "Player %s on account %s attempted to augment an unevolved item with augment type (Aug%i).\n(Item: %u, Aug1: %u, Aug2: %u, Aug3: %u, Aug4: %u, Aug5: %u)\n",
GetName(), account_name, (iter + 1), item->ID, aug1, aug2, aug3, aug4, aug5);
return false;
}
}
// check for augment to item restriction
if(enforcerestr) {
bool restrictfail = false;
uint8 it = item->ItemType;
switch(augtest->AugRestrict) {
case AugRestrAny:
break;
case AugRestrArmor:
switch(it) {
case ItemTypeArmor:
break;
default:
restrictfail = true;
break;
}
break;
case AugRestrWeapons:
switch(it) {
case ItemType1HSlash:
case ItemType1HBlunt:
case ItemType1HPiercing:
case ItemTypeMartial:
case ItemType2HSlash:
case ItemType2HBlunt:
case ItemType2HPiercing:
case ItemTypeBow:
break;
default:
restrictfail = true;
break;
}
break;
case AugRestr1HWeapons:
switch(it) {
case ItemType1HSlash:
case ItemType1HBlunt:
case ItemType1HPiercing:
case ItemTypeMartial:
break;
default:
restrictfail = true;
break;
}
break;
case AugRestr2HWeapons:
switch(it) {
case ItemType2HSlash:
case ItemType2HBlunt:
case ItemType2HPiercing:
case ItemTypeBow:
break;
default:
restrictfail = true;
break;
}
break;
case AugRestr1HSlash:
switch(it) {
case ItemType1HSlash:
break;
default:
restrictfail = true;
break;
}
break;
case AugRestr1HBlunt:
switch(it) {
case ItemType1HBlunt:
break;
default:
restrictfail = true;
break;
}
break;
case AugRestrPiercing:
switch(it) {
case ItemType1HPiercing:
break;
default:
restrictfail = true;
break;
}
break;
case AugRestrHandToHand:
switch(it) {
case ItemTypeMartial:
break;
default:
restrictfail = true;
break;
}
break;
case AugRestr2HSlash:
switch(it) {
case ItemType2HSlash:
break;
default:
restrictfail = true;
break;
}
break;
case AugRestr2HBlunt:
switch(it) {
case ItemType2HBlunt:
break;
default:
restrictfail = true;
break;
}
break;
case AugRestr2HPierce:
switch(it) {
case ItemType2HPiercing:
break;
default:
restrictfail = true;
break;
}
break;
case AugRestrBows:
switch(it) {
case ItemTypeBow:
break;
default:
restrictfail = true;
break;
}
break;
case AugRestrShields:
switch(it) {
case ItemTypeShield:
break;
default:
restrictfail = true;
break;
}
break;
case AugRestr1HSlash1HBluntOrHandToHand:
switch(it) {
case ItemType1HSlash:
case ItemType1HBlunt:
case ItemTypeMartial:
break;
default:
restrictfail = true;
break;
}
break;
case AugRestr1HBluntOrHandToHand:
switch(it) {
case ItemType1HBlunt:
case ItemTypeMartial:
break;
default:
restrictfail = true;
break;
}
break;
// These 3 are in-work
case AugRestrUnknown1:
case AugRestrUnknown2:
case AugRestrUnknown3:
default:
restrictfail = true;
break;
}
if(restrictfail) {
Message(13, "Augment %u (Aug%i) is restricted from wear on Item %u.", augments[iter], (iter + 1), item->ID);
mlog(INVENTORY__ERROR, "Player %s on account %s attempted to augment an item with a restricted augment (Aug%i).\n(Item: %u, Aug1: %u, Aug2: %u, Aug3: %u, Aug4: %u, Aug5: %u)\n",
GetName(), account_name, (iter + 1), item->ID, aug1, aug2, aug3, aug4, aug5);
return false;
}
}
if(enforceusable) {
// check for class usability
if(item->Classes && !(classes &= augtest->Classes)) {
Message(13, "Augment %u (Aug%i) will result in an item not usable by any class.", augments[iter], (iter + 1));
mlog(INVENTORY__ERROR, "Player %s on account %s attempted to create an item unusable by any class.\n(Item: %u, Aug1: %u, Aug2: %u, Aug3: %u, Aug4: %u, Aug5: %u)\n",
GetName(), account_name, item->ID, aug1, aug2, aug3, aug4, aug5);
return false;
}
// check for race usability
if(item->Races && !(races &= augtest->Races)) {
Message(13, "Augment %u (Aug%i) will result in an item not usable by any race.", augments[iter], (iter + 1));
mlog(INVENTORY__ERROR, "Player %s on account %s attempted to create an item unusable by any race.\n(Item: %u, Aug1: %u, Aug2: %u, Aug3: %u, Aug4: %u, Aug5: %u)\n",
GetName(), account_name, item->ID, aug1, aug2, aug3, aug4, aug5);
return false;
}
// check for slot usability
if(item->Slots && !(slots &= augtest->Slots)) {
Message(13, "Augment %u (Aug%i) will result in an item not usable in any slot.", augments[iter], (iter + 1));
mlog(INVENTORY__ERROR, "Player %s on account %s attempted to create an item unusable in any slot.\n(Item: %u, Aug1: %u, Aug2: %u, Aug3: %u, Aug4: %u, Aug5: %u)\n",
GetName(), account_name, item->ID, aug1, aug2, aug3, aug4, aug5);
return false;
}
}
}
}
// validation passed..so, set the charges and create the actual item
// if the item is stackable and the charge amount is -1 or 0 then set to 1 charge.
// removed && item->MaxCharges == 0 if -1 or 0 was passed max charges is irrelevant
if(charges <= 0 && item->Stackable)
charges = 1;
// if the charges is -1, then no charge value was passed in set to max charges
else if(charges == -1)
charges = item->MaxCharges;
// in any other situation just use charges as passed
ItemInst* inst = database.CreateItem(item, charges);
if(inst == nullptr) {
Message(13, "An unknown server error has occurred and your item was not created.");
// this goes to logfile since this is a major error
LogFile->write(EQEMuLog::Error, "Player %s on account %s encountered an unknown item creation error.\n(Item: %u, Aug1: %u, Aug2: %u, Aug3: %u, Aug4: %u, Aug5: %u)\n",
GetName(), account_name, item->ID, aug1, aug2, aug3, aug4, aug5);
return false;
}
// add any validated augments
for (int iter = AUG_BEGIN; iter < EmuConstants::ITEM_COMMON_SIZE; ++iter) {
if(augments[iter])
inst->PutAugment(&database, iter, augments[iter]);
}
// attune item
if(attuned && inst->GetItem()->Attuneable)
inst->SetInstNoDrop(true);
// check to see if item is usable in requested slot
if(enforceusable && (((to_slot >= MainCharm) && (to_slot <= MainAmmo)) || (to_slot == MainPowerSource))) {
uint32 slottest = (to_slot == MainPowerSource) ? 22 : to_slot; // can't change '22' just yet...
if(!(slots & ((uint32)1 << slottest))) {
Message(0, "This item is not equipable at slot %u - moving to cursor.", to_slot);
mlog(INVENTORY__ERROR, "Player %s on account %s attempted to equip an item unusable in slot %u - moved to cursor.\n(Item: %u, Aug1: %u, Aug2: %u, Aug3: %u, Aug4: %u, Aug5: %u)\n",
GetName(), account_name, to_slot, item->ID, aug1, aug2, aug3, aug4, aug5);
to_slot = MainCursor;
}
}
// put item into inventory
if (to_slot == MainCursor) {
PushItemOnCursor(*inst);
SendItemPacket(MainCursor, inst, ItemPacketSummonItem);
}
else {
PutItemInInventory(to_slot, *inst, true);
}
safe_delete(inst);
// discover item and any augments
if((RuleB(Character, EnableDiscoveredItems)) && !GetGM()) {
if(!IsDiscovered(item_id))
DiscoverItem(item_id);
for (int iter = AUG_BEGIN; iter < EmuConstants::ITEM_COMMON_SIZE; ++iter) {
if(augments[iter] && !IsDiscovered(augments[iter]))
DiscoverItem(augments[iter]);
}
}
return true;
}
// Drop item from inventory to ground (generally only dropped from SLOT_CURSOR)
void Client::DropItem(int16 slot_id)
{
if(GetInv().CheckNoDrop(slot_id) && RuleI(World, FVNoDropFlag) == 0 ||
RuleI(Character, MinStatusForNoDropExemptions) < Admin() && RuleI(World, FVNoDropFlag) == 2) {
database.SetHackerFlag(this->AccountName(), this->GetCleanName(), "Tried to drop an item on the ground that was nodrop!");
GetInv().DeleteItem(slot_id);
return;
}
// Take control of item in client inventory
ItemInst *inst = m_inv.PopItem(slot_id);
if(inst) {
int i = parse->EventItem(EVENT_DROP_ITEM, this, inst, nullptr, "", 0);
if(i != 0) {
safe_delete(inst);
}
} else {
// Item doesn't exist in inventory!
Message(13, "Error: Item not found in slot %i", slot_id);
return;
}
// Save client inventory change to database
if (slot_id == MainCursor) {
std::list<ItemInst*>::const_iterator s=m_inv.cursor_begin(),e=m_inv.cursor_end();
database.SaveCursor(CharacterID(), s, e);
} else {
database.SaveInventory(CharacterID(), nullptr, slot_id);
}
if(!inst)
return;
// Package as zone object
Object* object = new Object(this, inst);
entity_list.AddObject(object, true);
object->StartDecay();
safe_delete(inst);
}
// Drop inst
void Client::DropInst(const ItemInst* inst)
{
if (!inst) {
// Item doesn't exist in inventory!
Message(13, "Error: Item not found");
return;
}
if (inst->GetItem()->NoDrop == 0)
{
Message(13, "This item is NODROP. Deleting.");
return;
}
// Package as zone object
Object* object = new Object(this, inst);
entity_list.AddObject(object, true);
object->StartDecay();
}
// Returns a slot's item ID (returns INVALID_ID if not found)
int32 Client::GetItemIDAt(int16 slot_id) {
const ItemInst* inst = m_inv[slot_id];
if (inst)
return inst->GetItem()->ID;
// None found
return INVALID_ID;
}
// Returns an augment's ID that's in an item (returns INVALID_ID if not found)
// Pass in the slot ID of the item and which augslot you want to check (0-4)
int32 Client::GetAugmentIDAt(int16 slot_id, uint8 augslot) {
const ItemInst* inst = m_inv[slot_id];
if (inst)
if (inst->GetAugmentItemID(augslot))
return inst->GetAugmentItemID(augslot);
// None found
return INVALID_ID;
}
// Remove item from inventory
void Client::DeleteItemInInventory(int16 slot_id, int8 quantity, bool client_update, bool update_db) {
#if (EQDEBUG >= 5)
LogFile->write(EQEMuLog::Debug, "DeleteItemInInventory(%i, %i, %s)", slot_id, quantity, (client_update) ? "true":"false");
#endif
// Added 'IsSlotValid(slot_id)' check to both segments of client packet processing.
// - cursor queue slots were slipping through and crashing client
if(!m_inv[slot_id]) {
// Make sure the client deletes anything in this slot to match the server.
if(client_update && IsValidSlot(slot_id)) {
EQApplicationPacket* outapp;
outapp = new EQApplicationPacket(OP_DeleteItem, sizeof(DeleteItem_Struct));
DeleteItem_Struct* delitem = (DeleteItem_Struct*)outapp->pBuffer;
delitem->from_slot = slot_id;
delitem->to_slot = 0xFFFFFFFF;
delitem->number_in_stack = 0xFFFFFFFF;
QueuePacket(outapp);
safe_delete(outapp);
}
return;
}
// start QS code
if(RuleB(QueryServ, PlayerLogDeletes)) {
uint16 delete_count = 0;
if(m_inv[slot_id]) { delete_count += m_inv.GetItem(slot_id)->GetTotalItemCount(); }
ServerPacket* qspack = new ServerPacket(ServerOP_QSPlayerLogDeletes, sizeof(QSPlayerLogDelete_Struct) + (sizeof(QSDeleteItems_Struct) * delete_count));
QSPlayerLogDelete_Struct* qsaudit = (QSPlayerLogDelete_Struct*)qspack->pBuffer;
uint16 parent_offset = 0;
qsaudit->char_id = character_id;
qsaudit->stack_size = quantity;
qsaudit->char_count = delete_count;
qsaudit->items[parent_offset].char_slot = slot_id;
qsaudit->items[parent_offset].item_id = m_inv[slot_id]->GetID();
qsaudit->items[parent_offset].charges = m_inv[slot_id]->GetCharges();
qsaudit->items[parent_offset].aug_1 = m_inv[slot_id]->GetAugmentItemID(1);
qsaudit->items[parent_offset].aug_2 = m_inv[slot_id]->GetAugmentItemID(2);
qsaudit->items[parent_offset].aug_3 = m_inv[slot_id]->GetAugmentItemID(3);
qsaudit->items[parent_offset].aug_4 = m_inv[slot_id]->GetAugmentItemID(4);
qsaudit->items[parent_offset].aug_5 = m_inv[slot_id]->GetAugmentItemID(5);
if(m_inv[slot_id]->IsType(ItemClassContainer)) {
for(uint8 bag_idx = SUB_BEGIN; bag_idx < m_inv[slot_id]->GetItem()->BagSlots; bag_idx++) {
ItemInst* bagitem = m_inv[slot_id]->GetItem(bag_idx);
if(bagitem) {
int16 bagslot_id = Inventory::CalcSlotId(slot_id, bag_idx);
qsaudit->items[++parent_offset].char_slot = bagslot_id;
qsaudit->items[parent_offset].item_id = bagitem->GetID();
qsaudit->items[parent_offset].charges = bagitem->GetCharges();
qsaudit->items[parent_offset].aug_1 = bagitem->GetAugmentItemID(1);
qsaudit->items[parent_offset].aug_2 = bagitem->GetAugmentItemID(2);
qsaudit->items[parent_offset].aug_3 = bagitem->GetAugmentItemID(3);
qsaudit->items[parent_offset].aug_4 = bagitem->GetAugmentItemID(4);
qsaudit->items[parent_offset].aug_5 = bagitem->GetAugmentItemID(5);
}
}
}
qspack->Deflate();
if(worldserver.Connected()) { worldserver.SendPacket(qspack); }
safe_delete(qspack);
}
// end QS code
bool isDeleted = m_inv.DeleteItem(slot_id, quantity);
const ItemInst* inst=nullptr;
if (slot_id == MainCursor) {
std::list<ItemInst*>::const_iterator s=m_inv.cursor_begin(),e=m_inv.cursor_end();
if(update_db)
database.SaveCursor(character_id, s, e);
}
else {
// Save change to database
inst = m_inv[slot_id];
if(update_db)
database.SaveInventory(character_id, inst, slot_id);
}
if(client_update && IsValidSlot(slot_id)) {
EQApplicationPacket* outapp;
if(inst) {
if(!inst->IsStackable() && !isDeleted)
// Non stackable item with charges = Item with clicky spell effect ? Delete a charge.
outapp = new EQApplicationPacket(OP_DeleteCharge, sizeof(MoveItem_Struct));
else
// Stackable, arrows, etc ? Delete one from the stack
outapp = new EQApplicationPacket(OP_DeleteItem, sizeof(MoveItem_Struct));
DeleteItem_Struct* delitem = (DeleteItem_Struct*)outapp->pBuffer;
delitem->from_slot = slot_id;
delitem->to_slot = 0xFFFFFFFF;
delitem->number_in_stack = 0xFFFFFFFF;
for(int loop=0;loop<quantity;loop++)
QueuePacket(outapp);
safe_delete(outapp);
}
else {
outapp = new EQApplicationPacket(OP_MoveItem, sizeof(MoveItem_Struct));
MoveItem_Struct* delitem = (MoveItem_Struct*)outapp->pBuffer;
delitem->from_slot = slot_id;
delitem->to_slot = 0xFFFFFFFF;
delitem->number_in_stack = 0xFFFFFFFF;
QueuePacket(outapp);
safe_delete(outapp);
}
}
}
// Puts an item into the person's inventory
// Any items already there will be removed from user's inventory
// (Also saves changes back to the database: this may be optimized in the future)
// client_update: Sends packet to client
bool Client::PushItemOnCursor(const ItemInst& inst, bool client_update)
{
mlog(INVENTORY__SLOTS, "Putting item %s (%d) on the cursor", inst.GetItem()->Name, inst.GetItem()->ID);
m_inv.PushCursor(inst);
if (client_update) {
SendItemPacket(MainCursor, &inst, ItemPacketSummonItem);
}
std::list<ItemInst*>::const_iterator s=m_inv.cursor_begin(),e=m_inv.cursor_end();
return database.SaveCursor(CharacterID(), s, e);
}
bool Client::PutItemInInventory(int16 slot_id, const ItemInst& inst, bool client_update)
{
mlog(INVENTORY__SLOTS, "Putting item %s (%d) into slot %d", inst.GetItem()->Name, inst.GetItem()->ID, slot_id);
if (slot_id == MainCursor)
{
return PushItemOnCursor(inst,client_update);
}
else
m_inv.PutItem(slot_id, inst);
if (client_update) {
SendItemPacket(slot_id, &inst, (slot_id == MainCursor) ? ItemPacketSummonItem : ItemPacketTrade);
}
if (slot_id == MainCursor) {
std::list<ItemInst*>::const_iterator s=m_inv.cursor_begin(),e=m_inv.cursor_end();
return database.SaveCursor(this->CharacterID(), s, e);
} else
return database.SaveInventory(this->CharacterID(), &inst, slot_id);
CalcBonuses();
}
void Client::PutLootInInventory(int16 slot_id, const ItemInst &inst, ServerLootItem_Struct** bag_item_data)
{
mlog(INVENTORY__SLOTS, "Putting loot item %s (%d) into slot %d", inst.GetItem()->Name, inst.GetItem()->ID, slot_id);
m_inv.PutItem(slot_id, inst);
SendLootItemInPacket(&inst, slot_id);
if (slot_id == MainCursor) {
std::list<ItemInst*>::const_iterator s=m_inv.cursor_begin(),e=m_inv.cursor_end();
database.SaveCursor(this->CharacterID(), s, e);
} else
database.SaveInventory(this->CharacterID(), &inst, slot_id);
if(bag_item_data) // bag contents
{
int16 interior_slot;
// solar: our bag went into slot_id, now let's pack the contents in
for(int i = SUB_BEGIN; i < EmuConstants::ITEM_CONTAINER_SIZE; i++)
{
if(bag_item_data[i] == nullptr)
continue;
const ItemInst *bagitem = database.CreateItem(bag_item_data[i]->item_id, bag_item_data[i]->charges, bag_item_data[i]->aug1, bag_item_data[i]->aug2, bag_item_data[i]->aug3, bag_item_data[i]->aug4, bag_item_data[i]->aug5);
interior_slot = Inventory::CalcSlotId(slot_id, i);
mlog(INVENTORY__SLOTS, "Putting bag loot item %s (%d) into slot %d (bag slot %d)", inst.GetItem()->Name, inst.GetItem()->ID, interior_slot, i);
PutLootInInventory(interior_slot, *bagitem);
safe_delete(bagitem);
}
}
CalcBonuses();
}
bool Client::TryStacking(ItemInst* item, uint8 type, bool try_worn, bool try_cursor){
if(!item || !item->IsStackable() || item->GetCharges()>=item->GetItem()->StackSize)
return false;
int16 i;
uint32 item_id = item->GetItem()->ID;
for (i = EmuConstants::GENERAL_BEGIN; i <= EmuConstants::GENERAL_END; i++)
{
ItemInst* tmp_inst = m_inv.GetItem(i);
if(tmp_inst && tmp_inst->GetItem()->ID == item_id && tmp_inst->GetCharges() < tmp_inst->GetItem()->StackSize){
MoveItemCharges(*item, i, type);
CalcBonuses();
if(item->GetCharges()) // we didn't get them all
return AutoPutLootInInventory(*item, try_worn, try_cursor, 0);
return true;
}
}
for (i = EmuConstants::GENERAL_BEGIN; i <= EmuConstants::GENERAL_END; i++)
{
for (uint8 j = SUB_BEGIN; j < EmuConstants::ITEM_CONTAINER_SIZE; j++)
{
uint16 slotid = Inventory::CalcSlotId(i, j);
ItemInst* tmp_inst = m_inv.GetItem(slotid);
if(tmp_inst && tmp_inst->GetItem()->ID == item_id && tmp_inst->GetCharges() < tmp_inst->GetItem()->StackSize){
MoveItemCharges(*item, slotid, type);
CalcBonuses();
if(item->GetCharges()) // we didn't get them all
return AutoPutLootInInventory(*item, try_worn, try_cursor, 0);
return true;
}
}
}
return false;
}
// Locate an available space in inventory to place an item
// and then put the item there
// The change will be saved to the database
bool Client::AutoPutLootInInventory(ItemInst& inst, bool try_worn, bool try_cursor, ServerLootItem_Struct** bag_item_data)
{
// #1: Try to auto equip
if (try_worn && inst.IsEquipable(GetBaseRace(), GetClass()) && inst.GetItem()->ReqLevel<=level && !inst.GetItem()->Attuneable && inst.GetItem()->ItemType != ItemTypeAugmentation)
{
// too messy as-is... <watch>
for (int16 i = EmuConstants::EQUIPMENT_BEGIN; i < MainPowerSource; i++) // originally (i < 22)
{
if (i == EmuConstants::GENERAL_BEGIN) {
if(this->GetClientVersion() >= EQClientSoF) { i = MainPowerSource; } // added power source check for SoF+ clients
else { break; }
}
if (!m_inv[i])
{
if( i == MainPrimary && inst.IsWeapon() ) // If item is primary slot weapon
{
if( (inst.GetItem()->ItemType == ItemType2HSlash) || (inst.GetItem()->ItemType == ItemType2HBlunt) || (inst.GetItem()->ItemType == ItemType2HPiercing) ) // and uses 2hs \ 2hb \ 2hp
{
if( m_inv[MainSecondary] ) // and if secondary slot is not empty
{
continue; // Can't auto-equip
}
}
}
if( i== MainSecondary && m_inv[MainPrimary]) // check to see if primary slot is a two hander
{
uint8 use = m_inv[MainPrimary]->GetItem()->ItemType;
if(use == ItemType2HSlash || use == ItemType2HBlunt || use == ItemType2HPiercing)
continue;
}
if
(
i == MainSecondary &&
inst.IsWeapon() &&
!CanThisClassDualWield()
)
{
continue;
}
if (inst.IsEquipable(i)) // Equippable at this slot?
{
//send worn to everyone...
PutLootInInventory(i, inst);
uint8 worn_slot_material = Inventory::CalcMaterialFromSlot(i);
if(worn_slot_material != _MaterialInvalid)
{
SendWearChange(worn_slot_material);
}
parse->EventItem(EVENT_EQUIP_ITEM, this, &inst, nullptr, "", i);
return true;
}
}
}
}
// #2: Stackable item?
if (inst.IsStackable())
{
if(TryStacking(&inst, ItemPacketTrade, try_worn, try_cursor))
return true;
}
// #3: put it in inventory
bool is_arrow = (inst.GetItem()->ItemType == ItemTypeArrow) ? true : false;
int16 slot_id = m_inv.FindFreeSlot(inst.IsType(ItemClassContainer), try_cursor, inst.GetItem()->Size, is_arrow);
if (slot_id != INVALID_INDEX)
{
PutLootInInventory(slot_id, inst, bag_item_data);
return true;
}
return false;
}
// solar: helper function for AutoPutLootInInventory
void Client::MoveItemCharges(ItemInst &from, int16 to_slot, uint8 type)
{
ItemInst *tmp_inst = m_inv.GetItem(to_slot);
if(tmp_inst && tmp_inst->GetCharges() < tmp_inst->GetItem()->StackSize)
{
// this is how much room is left on the item we're stacking onto
int charge_slots_left = tmp_inst->GetItem()->StackSize - tmp_inst->GetCharges();
// this is how many charges we can move from the looted item to
// the item in the inventory
int charges_to_move =
from.GetCharges() < charge_slots_left ?
from.GetCharges() :
charge_slots_left;
tmp_inst->SetCharges(tmp_inst->GetCharges() + charges_to_move);
from.SetCharges(from.GetCharges() - charges_to_move);
SendLootItemInPacket(tmp_inst, to_slot);
if (to_slot == MainCursor){
std::list<ItemInst*>::const_iterator s=m_inv.cursor_begin(),e=m_inv.cursor_end();
database.SaveCursor(this->CharacterID(), s, e);
} else
database.SaveInventory(this->CharacterID(), tmp_inst, to_slot);
}
}
bool Client::MakeItemLink(char* &ret_link, const ItemInst *inst) {
//we're sending back the entire "link", minus the null characters & item name
//that way, we can use it for regular links & Task links
//note: initiator needs to pass us ret_link
/*
--- Usage ---
Chat: "%c" "%s" "%s" "%c", 0x12, ret_link, inst->GetItem()->name, 0x12
Task: "<a WndNotify=\"27," "%s" "\">" "%s" "</a>", ret_link, inst->GetItem()->name
<a WndNotify="27,00960F000000000000000000000000000000000000000">Master's Book of Wood Elven Culture</a>
http://eqitems.13th-floor.org/phpBB2/viewtopic.php?p=510#510
*/
if (!inst) //have to have an item to make the link
return false;
const Item_Struct* item = inst->GetItem();
//format:
//0 itemid aug1 aug2 aug3 aug4 aug5 evolving? loregroup evolved level hash
//0 00000 00000 00000 00000 00000 00000 0 0000 0 00000000
//length:
//1 5 5 5 5 5 5 1 4 1 8 = 45
//evolving item info: http://eqitems.13th-floor.org/phpBB2/viewtopic.php?t=145#558
uint8 evolving = 0;
uint16 loregroup = 0;
uint8 evolvedlevel = 0;
int hash = 0;
//int hash = GetItemLinkHash(inst); //eventually this will work (currently crashes zone), but for now we'll skip the extra overhead
if (GetClientVersion() >= EQClientRoF)
{
MakeAnyLenString(&ret_link, "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%1X" "%05X" "%08X",
0,
item->ID,
inst->GetAugmentItemID(0),
inst->GetAugmentItemID(1),
inst->GetAugmentItemID(2),
inst->GetAugmentItemID(3),
inst->GetAugmentItemID(4),
inst->GetAugmentItemID(5),
evolving,
loregroup,
evolvedlevel,
0,
hash
);
}
else if (GetClientVersion() >= EQClientSoF)
{
MakeAnyLenString(&ret_link, "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%1X" "%05X" "%08X",
0,
item->ID,
inst->GetAugmentItemID(0),
inst->GetAugmentItemID(1),
inst->GetAugmentItemID(2),
inst->GetAugmentItemID(3),
inst->GetAugmentItemID(4),
evolving,
loregroup,
evolvedlevel,
0,
hash
);
}
else
{
MakeAnyLenString(&ret_link, "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%1X" "%08X",
0,
item->ID,
inst->GetAugmentItemID(0),
inst->GetAugmentItemID(1),
inst->GetAugmentItemID(2),
inst->GetAugmentItemID(3),
inst->GetAugmentItemID(4),
evolving,
loregroup,
evolvedlevel,
hash
);
}
return true;
}
int Client::GetItemLinkHash(const ItemInst* inst) {
//pre-Titanium: http://eqitems.13th-floor.org/phpBB2/viewtopic.php?t=70&postdays=0&postorder=asc
//Titanium: http://eqitems.13th-floor.org/phpBB2/viewtopic.php?t=145
if (!inst) //have to have an item to make the hash
return 0;
const Item_Struct* item = inst->GetItem();
char* hash_str = 0;
/*register */int hash = 0;
//now the fun part, since different types of items use different hashes...
if (item->ItemClass == 0 && item->CharmFileID) { //charm
MakeAnyLenString(&hash_str, "%d%s-1-1-1-1-1%d %d %d %d %d %d %d %d %d",
item->ID,
item->Name,
item->Light,
item->Icon,
item->Price,
item->Size,
item->Weight,
item->ItemClass,
item->ItemType,
item->Favor,
item->GuildFavor);
} else if (item->ItemClass == 2) { //book
MakeAnyLenString(&hash_str, "%d%s%d%d%09X",
item->ID,
item->Name,
item->Weight,
item->BookType,
item->Price);
} else if (item->ItemClass == 1) { //bag
MakeAnyLenString(&hash_str, "%d%s%x%d%09X%d",
item->ID,
item->Name,
item->BagSlots,
item->BagWR,
item->Price,
item->Weight);
} else { //everything else
MakeAnyLenString(&hash_str, "%d%s-1-1-1-1-1%d %d %d %d %d %d %d %d %d %d %d %d %d",
item->ID,
item->Name,
item->Mana,
item->HP,
item->Favor,
item->Light,
item->Icon,
item->Price,
item->Weight,
item->ReqLevel,
item->Size,
item->ItemClass,
item->ItemType,
item->AC,
item->GuildFavor);
}
//this currently crashes zone, so someone feel free to fix this so we can work with hashes:
//*** glibc detected *** double free or corruption (out): 0xb2403470 ***
/*
while (*hash_str != '\0') {
register int c = toupper(*hash_str);
asm volatile("\
imul $31, %1, %1;\
movzx %%ax, %%edx;\
addl %%edx, %1;\
movl %1, %0;\
"
:"=r"(hash)
:"D"(hash), "a"(c)
:"%edx"
);
// This is what the inline asm is doing:
// hash *= 0x1f;
// hash += (int)c;
hash_str++;
}
*/
safe_delete_array(hash_str);
return hash;
}
void Client::SendItemLink(const ItemInst* inst, bool send_to_all)
{
/*
this stuff is old, live dosent do this anymore. they send a much smaller
packet with the item number in it, but I cant seem to find it right now
*/
if (!inst)
return;
const Item_Struct* item = inst->GetItem();
const char* name2 = &item->Name[0];
EQApplicationPacket* outapp = new EQApplicationPacket(OP_ItemLinkText,strlen(name2)+68);
char buffer2[135] = {0};
char itemlink[135] = {0};
sprintf(itemlink,"%c0%06u0%05u-%05u-%05u-%05u-%05u00000000%c",
0x12,
item->ID,
inst->GetAugmentItemID(0),
inst->GetAugmentItemID(1),
inst->GetAugmentItemID(2),
inst->GetAugmentItemID(3),
inst->GetAugmentItemID(4),
0x12);
sprintf(buffer2,"%c%c%c%c%c%c%c%c%c%c%c%c%s",0x00,0x00,0x00,0x00,0xD3,0x01,0x00,0x00,0x1E,0x01,0x00,0x00,itemlink);
memcpy(outapp->pBuffer,buffer2,outapp->size);
QueuePacket(outapp);
safe_delete(outapp);
if (send_to_all==false)
return;
const char* charname = this->GetName();
outapp = new EQApplicationPacket(OP_ItemLinkText,strlen(itemlink)+14+strlen(charname));
char buffer3[150] = {0};
sprintf(buffer3,"%c%c%c%c%c%c%c%c%c%c%c%c%6s%c%s",0x00,0x00,0x00,0x00,0xD2,0x01,0x00,0x00,0x00,0x00,0x00,0x00,charname,0x00,itemlink);
memcpy(outapp->pBuffer,buffer3,outapp->size);
entity_list.QueueCloseClients(this->CastToMob(),outapp,true,200,0,false);
safe_delete(outapp);
}
void Client::SendLootItemInPacket(const ItemInst* inst, int16 slot_id)
{
SendItemPacket(slot_id,inst, ItemPacketTrade);
}
bool Client::IsValidSlot(uint32 slot) {
if ((slot == (uint32)INVALID_INDEX) ||
(slot >= MAIN_BEGIN && slot < EmuConstants::MAP_POSSESSIONS_SIZE) ||
(slot >= EmuConstants::GENERAL_BAGS_BEGIN && slot <= EmuConstants::CURSOR_BAG_END) ||
(slot >= EmuConstants::TRIBUTE_BEGIN && slot <= EmuConstants::TRIBUTE_END) ||
(slot >= EmuConstants::BANK_BEGIN && slot <= EmuConstants::BANK_END) ||
(slot >= EmuConstants::BANK_BAGS_BEGIN && slot <= EmuConstants::BANK_BAGS_END) ||
(slot >= EmuConstants::SHARED_BANK_BEGIN && slot <= EmuConstants::SHARED_BANK_END) ||
(slot >= EmuConstants::SHARED_BANK_BAGS_BEGIN && slot <= EmuConstants::SHARED_BANK_BAGS_END) ||
(slot >= EmuConstants::TRADE_BEGIN && slot <= EmuConstants::TRADE_END) ||
(slot >= EmuConstants::WORLD_BEGIN && slot <= EmuConstants::WORLD_END) ||
(slot == MainPowerSource))
return true;
else
return false;
}
bool Client::IsBankSlot(uint32 slot)
{
if ((slot >= EmuConstants::BANK_BEGIN && slot <= EmuConstants::BANK_END) ||
(slot >= EmuConstants::BANK_BAGS_BEGIN && slot <= EmuConstants::BANK_BAGS_END) ||
(slot >= EmuConstants::SHARED_BANK_BEGIN && slot <= EmuConstants::SHARED_BANK_END) ||
(slot >= EmuConstants::SHARED_BANK_BAGS_BEGIN && slot <= EmuConstants::SHARED_BANK_BAGS_END))
{
return true;
}
return false;
}
// Moves items around both internally and in the database
// In the future, this can be optimized by pushing all changes through one database REPLACE call
bool Client::SwapItem(MoveItem_Struct* move_in) {
uint32 src_slot_check = move_in->from_slot;
uint32 dst_slot_check = move_in->to_slot;
uint32 stack_count_check = move_in->number_in_stack;
if(!IsValidSlot(src_slot_check)){
// SoF+ sends a Unix timestamp (should be int32) for src and dst slots every 10 minutes for some reason.
if(src_slot_check < 2147483647)
Message(13, "Warning: Invalid slot move from slot %u to slot %u with %u charges!", src_slot_check, dst_slot_check, stack_count_check);
mlog(INVENTORY__SLOTS, "Invalid slot move from slot %u to slot %u with %u charges!", src_slot_check, dst_slot_check, stack_count_check);
return false;
}
if(!IsValidSlot(dst_slot_check)) {
// SoF+ sends a Unix timestamp (should be int32) for src and dst slots every 10 minutes for some reason.
if(src_slot_check < 2147483647)
Message(13, "Warning: Invalid slot move from slot %u to slot %u with %u charges!", src_slot_check, dst_slot_check, stack_count_check);
mlog(INVENTORY__SLOTS, "Invalid slot move from slot %u to slot %u with %u charges!", src_slot_check, dst_slot_check, stack_count_check);
return false;
}
// This could be expounded upon at some point to let the server know that
// the client has moved a buffered cursor item onto the active cursor -U
if (move_in->from_slot == move_in->to_slot) { // Item summon, no further proccessing needed
if(RuleB(QueryServ, PlayerLogMoves)) { QSSwapItemAuditor(move_in); } // QS Audit
return true;
}
if (move_in->to_slot == (uint32)INVALID_INDEX) {
if (move_in->from_slot == (uint32)MainCursor) {
mlog(INVENTORY__SLOTS, "Client destroyed item from cursor slot %d", move_in->from_slot);
if(RuleB(QueryServ, PlayerLogMoves)) { QSSwapItemAuditor(move_in); } // QS Audit
ItemInst *inst = m_inv.GetItem(MainCursor);
if(inst) {
parse->EventItem(EVENT_DESTROY_ITEM, this, inst, nullptr, "", 0);
}
DeleteItemInInventory(move_in->from_slot);
return true; // Item destroyed by client
}
else {
mlog(INVENTORY__SLOTS, "Deleted item from slot %d as a result of an inventory container tradeskill combine.", move_in->from_slot);
if(RuleB(QueryServ, PlayerLogMoves)) { QSSwapItemAuditor(move_in); } // QS Audit
DeleteItemInInventory(move_in->from_slot);
return true; // Item deletetion
}
}
if(auto_attack && (move_in->from_slot == MainPrimary || move_in->from_slot == MainSecondary || move_in->from_slot == MainRange))
SetAttackTimer();
else if(auto_attack && (move_in->to_slot == MainPrimary || move_in->to_slot == MainSecondary || move_in->to_slot == MainRange))
SetAttackTimer();
// Step 1: Variables
int16 src_slot_id = (int16)move_in->from_slot;
int16 dst_slot_id = (int16)move_in->to_slot;
if(IsBankSlot(src_slot_id) ||
IsBankSlot(dst_slot_id) ||
IsBankSlot(src_slot_check) ||
IsBankSlot(dst_slot_check))
{
uint32 distance = 0;
NPC *banker = entity_list.GetClosestBanker(this, distance);
if(!banker || distance > USE_NPC_RANGE2)
{
char *hacked_string = nullptr;
MakeAnyLenString(&hacked_string, "Player tried to make use of a banker(items) but %s is non-existant or too far away (%u units).",
banker ? banker->GetName() : "UNKNOWN NPC", distance);
database.SetMQDetectionFlag(AccountName(), GetName(), hacked_string, zone->GetShortName());
safe_delete_array(hacked_string);
Kick(); // Kicking player to avoid item loss do to client and server inventories not being sync'd
return false;
}
}
//Setup
uint32 srcitemid = 0;
uint32 dstitemid = 0;
ItemInst* src_inst = m_inv.GetItem(src_slot_id);
ItemInst* dst_inst = m_inv.GetItem(dst_slot_id);
if (src_inst){
mlog(INVENTORY__SLOTS, "Src slot %d has item %s (%d) with %d charges in it.", src_slot_id, src_inst->GetItem()->Name, src_inst->GetItem()->ID, src_inst->GetCharges());
srcitemid = src_inst->GetItem()->ID;
//SetTint(dst_slot_id,src_inst->GetColor());
if (src_inst->GetCharges() > 0 && (src_inst->GetCharges() < (int16)move_in->number_in_stack || move_in->number_in_stack > src_inst->GetItem()->StackSize))
{
Message(13,"Error: Insufficent number in stack.");
return false;
}
}
if (dst_inst) {
mlog(INVENTORY__SLOTS, "Dest slot %d has item %s (%d) with %d charges in it.", dst_slot_id, dst_inst->GetItem()->Name, dst_inst->GetItem()->ID, dst_inst->GetCharges());
dstitemid = dst_inst->GetItem()->ID;
}
if (Trader && srcitemid>0){
ItemInst* srcbag;
ItemInst* dstbag;
uint32 srcbagid =0;
uint32 dstbagid = 0;
//if (src_slot_id >= 250 && src_slot_id < 330) {
if (src_slot_id >= EmuConstants::GENERAL_BAGS_BEGIN && src_slot_id <= EmuConstants::GENERAL_BAGS_END) {
srcbag = m_inv.GetItem(((int)(src_slot_id / 10)) - 3);
if (srcbag)
srcbagid = srcbag->GetItem()->ID;
}
//if (dst_slot_id >= 250 && dst_slot_id < 330) {
if (dst_slot_id >= EmuConstants::GENERAL_BAGS_BEGIN && dst_slot_id <= EmuConstants::GENERAL_BAGS_END) {
dstbag = m_inv.GetItem(((int)(dst_slot_id / 10)) - 3);
if (dstbag)
dstbagid = dstbag->GetItem()->ID;
}
if (srcitemid==17899 || srcbagid==17899 || dstitemid==17899 || dstbagid==17899){
this->Trader_EndTrader();
this->Message(13,"You cannot move your Trader Satchels, or items inside them, while Trading.");
}
}
// Step 2: Validate item in from_slot
// After this, we can assume src_inst is a valid ptr
if (!src_inst && (src_slot_id < EmuConstants::WORLD_BEGIN || src_slot_id > EmuConstants::WORLD_END)) {
if (dst_inst) {
// If there is no source item, but there is a destination item,
// move the slots around before deleting the invalid source slot item,
// which is now in the destination slot.
move_in->from_slot = dst_slot_check;
move_in->to_slot = src_slot_check;
move_in->number_in_stack = dst_inst->GetCharges();
if(!SwapItem(move_in)) { mlog(INVENTORY__ERROR, "Recursive SwapItem call failed due to non-existent destination item (charid: %i, fromslot: %i, toslot: %i)", CharacterID(), src_slot_id, dst_slot_id); }
}
return false;
}
//verify shared bank transactions in the database
if(src_inst && src_slot_id >= EmuConstants::SHARED_BANK_BEGIN && src_slot_id <= EmuConstants::SHARED_BANK_BAGS_END) {
if(!database.VerifyInventory(account_id, src_slot_id, src_inst)) {
LogFile->write(EQEMuLog::Error, "Player %s on account %s was found exploiting the shared bank.\n", GetName(), account_name);
DeleteItemInInventory(dst_slot_id,0,true);
return(false);
}
if(src_slot_id >= EmuConstants::SHARED_BANK_BEGIN && src_slot_id <= EmuConstants::SHARED_BANK_END && src_inst->IsType(ItemClassContainer)){
for (uint8 idx = SUB_BEGIN; idx < EmuConstants::ITEM_CONTAINER_SIZE; idx++) {
const ItemInst* baginst = src_inst->GetItem(idx);
if(baginst && !database.VerifyInventory(account_id, Inventory::CalcSlotId(src_slot_id, idx), baginst)){
DeleteItemInInventory(Inventory::CalcSlotId(src_slot_id, idx),0,false);
}
}
}
}
if(dst_inst && dst_slot_id >= EmuConstants::SHARED_BANK_BEGIN && dst_slot_id <= EmuConstants::SHARED_BANK_BAGS_END) {
if(!database.VerifyInventory(account_id, dst_slot_id, dst_inst)) {
LogFile->write(EQEMuLog::Error, "Player %s on account %s was found exploting the shared bank.\n", GetName(), account_name);
DeleteItemInInventory(src_slot_id,0,true);
return(false);
}
if(dst_slot_id >= EmuConstants::SHARED_BANK_BEGIN && dst_slot_id <= EmuConstants::SHARED_BANK_END && dst_inst->IsType(ItemClassContainer)){
for (uint8 idx = SUB_BEGIN; idx < EmuConstants::ITEM_CONTAINER_SIZE; idx++) {
const ItemInst* baginst = dst_inst->GetItem(idx);
if(baginst && !database.VerifyInventory(account_id, Inventory::CalcSlotId(dst_slot_id, idx), baginst)){
DeleteItemInInventory(Inventory::CalcSlotId(dst_slot_id, idx),0,false);
}
}
}
}
// Check for No Drop Hacks
Mob* with = trade->With();
if (((with && with->IsClient() && dst_slot_id >= EmuConstants::TRADE_BEGIN && dst_slot_id <= EmuConstants::TRADE_END) ||
(dst_slot_id >= EmuConstants::SHARED_BANK_BEGIN && dst_slot_id <= EmuConstants::SHARED_BANK_BAGS_END))
&& GetInv().CheckNoDrop(src_slot_id)
&& RuleI(World, FVNoDropFlag) == 0 || RuleI(Character, MinStatusForNoDropExemptions) < Admin() && RuleI(World, FVNoDropFlag) == 2) {
DeleteItemInInventory(src_slot_id);
WorldKick();
return false;
}
// Step 3: Check for interaction with World Container (tradeskills)
if(m_tradeskill_object != nullptr) {
if (src_slot_id >= EmuConstants::WORLD_BEGIN && src_slot_id <= EmuConstants::WORLD_END) {
// Picking up item from world container
ItemInst* inst = m_tradeskill_object->PopItem(Inventory::CalcBagIdx(src_slot_id));
if (inst) {
PutItemInInventory(dst_slot_id, *inst, false);
safe_delete(inst);
}
if(RuleB(QueryServ, PlayerLogMoves)) { QSSwapItemAuditor(move_in, true); } // QS Audit
return true;
}
else if (dst_slot_id >= EmuConstants::WORLD_BEGIN && dst_slot_id <= EmuConstants::WORLD_END) {
// Putting item into world container, which may swap (or pile onto) with existing item
uint8 world_idx = Inventory::CalcBagIdx(dst_slot_id);
ItemInst* world_inst = m_tradeskill_object->PopItem(world_idx);
// Case 1: No item in container, unidirectional "Put"
if (world_inst == nullptr) {
m_tradeskill_object->PutItem(world_idx, src_inst);
m_inv.DeleteItem(src_slot_id);
}
else {
const Item_Struct* world_item = world_inst->GetItem();
const Item_Struct* src_item = src_inst->GetItem();
if (world_item && src_item) {
// Case 2: Same item on cursor, stacks, transfer of charges needed
if ((world_item->ID == src_item->ID) && src_inst->IsStackable()) {
int16 world_charges = world_inst->GetCharges();
int16 src_charges = src_inst->GetCharges();
// Fill up destination stack as much as possible
world_charges += src_charges;
if (world_charges > world_inst->GetItem()->StackSize) {
src_charges = world_charges - world_inst->GetItem()->StackSize;
world_charges = world_inst->GetItem()->StackSize;
}
else {
src_charges = 0;
}
world_inst->SetCharges(world_charges);
m_tradeskill_object->Save();
if (src_charges == 0) {
m_inv.DeleteItem(src_slot_id); // DB remove will occur below
}
else {
src_inst->SetCharges(src_charges);
}
}
else {
// Case 3: Swap the item on user with item in world container
// World containers don't follow normal rules for swapping
ItemInst* inv_inst = m_inv.PopItem(src_slot_id);
m_tradeskill_object->PutItem(world_idx, inv_inst);
m_inv.PutItem(src_slot_id, *world_inst);
safe_delete(inv_inst);
}
}
}
safe_delete(world_inst);
if (src_slot_id == MainCursor) {
std::list<ItemInst*>::const_iterator s=m_inv.cursor_begin(),e=m_inv.cursor_end();
database.SaveCursor(character_id, s, e);
} else
database.SaveInventory(character_id, m_inv[src_slot_id], src_slot_id);
if(RuleB(QueryServ, PlayerLogMoves)) { QSSwapItemAuditor(move_in, true); } // QS Audit
return true;
}
}
// Step 4: Check for entity trade
if (dst_slot_id >= EmuConstants::TRADE_BEGIN && dst_slot_id <= EmuConstants::TRADE_END) {
if (src_slot_id != MainCursor) {
Kick();
return false;
}
if (with) {
mlog(INVENTORY__SLOTS, "Trade item move from slot %d to slot %d (trade with %s)", src_slot_id, dst_slot_id, with->GetName());
// Fill Trade list with items from cursor
if (!m_inv[MainCursor]) {
Message(13, "Error: Cursor item not located on server!");
return false;
}
// Add cursor item to trade bucket
// Also sends trade information to other client of trade session
if(RuleB(QueryServ, PlayerLogMoves)) { QSSwapItemAuditor(move_in); } // QS Audit
trade->AddEntity(src_slot_id, dst_slot_id);
return true;
} else {
if(RuleB(QueryServ, PlayerLogMoves)) { QSSwapItemAuditor(move_in); } // QS Audit
SummonItem(src_inst->GetID(), src_inst->GetCharges());
DeleteItemInInventory(MainCursor);
return true;
}
}
// Step 5: Swap (or stack) items
if (move_in->number_in_stack > 0) {
// Determine if charged items can stack
if(src_inst && !src_inst->IsStackable()) {
mlog(INVENTORY__ERROR, "Move from %d to %d with stack size %d. %s is not a stackable item. (charname: %s)", src_slot_id, dst_slot_id, move_in->number_in_stack, src_inst->GetItem()->Name, GetName());
return false;
}
if (dst_inst) {
if(src_inst->GetID() != dst_inst->GetID()) {
mlog(INVENTORY__ERROR, "Move from %d to %d with stack size %d. Incompatible item types: %d != %d", src_slot_id, dst_slot_id, move_in->number_in_stack, src_inst->GetID(), dst_inst->GetID());
return(false);
}
if(dst_inst->GetCharges() < dst_inst->GetItem()->StackSize) {
//we have a chance of stacking.
mlog(INVENTORY__SLOTS, "Move from %d to %d with stack size %d. dest has %d/%d charges", src_slot_id, dst_slot_id, move_in->number_in_stack, dst_inst->GetCharges(), dst_inst->GetItem()->StackSize);
// Charges can be emptied into dst
uint16 usedcharges = dst_inst->GetItem()->StackSize - dst_inst->GetCharges();
if (usedcharges > move_in->number_in_stack)
usedcharges = move_in->number_in_stack;
dst_inst->SetCharges(dst_inst->GetCharges() + usedcharges);
src_inst->SetCharges(src_inst->GetCharges() - usedcharges);
// Depleted all charges?
if (src_inst->GetCharges() < 1)
{
mlog(INVENTORY__SLOTS, "Dest (%d) now has %d charges, source (%d) was entirely consumed. (%d moved)", dst_slot_id, dst_inst->GetCharges(), src_slot_id, usedcharges);
database.SaveInventory(CharacterID(),nullptr,src_slot_id);
m_inv.DeleteItem(src_slot_id);
} else {
mlog(INVENTORY__SLOTS, "Dest (%d) now has %d charges, source (%d) has %d (%d moved)", dst_slot_id, dst_inst->GetCharges(), src_slot_id, src_inst->GetCharges(), usedcharges);
}
} else {
mlog(INVENTORY__ERROR, "Move from %d to %d with stack size %d. Exceeds dest maximum stack size: %d/%d", src_slot_id, dst_slot_id, move_in->number_in_stack, (src_inst->GetCharges()+dst_inst->GetCharges()), dst_inst->GetItem()->StackSize);
return false;
}
}
else {
// Nothing in destination slot: split stack into two
if ((int16)move_in->number_in_stack >= src_inst->GetCharges()) {
// Move entire stack
if(!m_inv.SwapItem(src_slot_id, dst_slot_id)) { return false; }
mlog(INVENTORY__SLOTS, "Move entire stack from %d to %d with stack size %d. Dest empty.", src_slot_id, dst_slot_id, move_in->number_in_stack);
}
else {
// Split into two
src_inst->SetCharges(src_inst->GetCharges() - move_in->number_in_stack);
mlog(INVENTORY__SLOTS, "Split stack of %s (%d) from slot %d to %d with stack size %d. Src keeps %d.", src_inst->GetItem()->Name, src_inst->GetItem()->ID, src_slot_id, dst_slot_id, move_in->number_in_stack, src_inst->GetCharges());
ItemInst* inst = database.CreateItem(src_inst->GetItem(), move_in->number_in_stack);
m_inv.PutItem(dst_slot_id, *inst);
safe_delete(inst);
}
}
}
else {
// Not dealing with charges - just do direct swap
if(src_inst && (dst_slot_id <= EmuConstants::EQUIPMENT_END || dst_slot_id == MainPowerSource) && dst_slot_id >= EmuConstants::EQUIPMENT_BEGIN) {
if (src_inst->GetItem()->Attuneable) {
src_inst->SetInstNoDrop(true);
}
if (src_inst->IsAugmented()) {
for (int i = AUG_BEGIN; i < EmuConstants::ITEM_COMMON_SIZE; i++) {
if (src_inst->GetAugment(i)) {
if (src_inst->GetAugment(i)->GetItem()->Attuneable) {
src_inst->GetAugment(i)->SetInstNoDrop(true);
}
}
}
}
SetMaterial(dst_slot_id,src_inst->GetItem()->ID);
}
if(!m_inv.SwapItem(src_slot_id, dst_slot_id)) { return false; }
mlog(INVENTORY__SLOTS, "Moving entire item from slot %d to slot %d", src_slot_id, dst_slot_id);
if(src_slot_id <= EmuConstants::EQUIPMENT_END || src_slot_id == MainPowerSource) {
if(src_inst) {
parse->EventItem(EVENT_UNEQUIP_ITEM, this, src_inst, nullptr, "", src_slot_id);
}
if(dst_inst) {
parse->EventItem(EVENT_EQUIP_ITEM, this, dst_inst, nullptr, "", src_slot_id);
}
}
if(dst_slot_id <= EmuConstants::EQUIPMENT_END || dst_slot_id == MainPowerSource) {
if(dst_inst) {
parse->EventItem(EVENT_UNEQUIP_ITEM, this, dst_inst, nullptr, "", dst_slot_id);
}
if(src_inst) {
parse->EventItem(EVENT_EQUIP_ITEM, this, src_inst, nullptr, "", dst_slot_id);
}
}
}
int matslot = SlotConvert2(dst_slot_id);
if (dst_slot_id <= EmuConstants::EQUIPMENT_END && matslot != MaterialHead) { // think this is to allow the client to update with /showhelm
SendWearChange(matslot);
}
// Step 7: Save change to the database
if (src_slot_id == MainCursor){
std::list<ItemInst*>::const_iterator s=m_inv.cursor_begin(),e=m_inv.cursor_end();
database.SaveCursor(character_id, s, e);
} else
database.SaveInventory(character_id, m_inv.GetItem(src_slot_id), src_slot_id);
if (dst_slot_id == MainCursor) {
std::list<ItemInst*>::const_iterator s=m_inv.cursor_begin(),e=m_inv.cursor_end();
database.SaveCursor(character_id, s, e);
} else
database.SaveInventory(character_id, m_inv.GetItem(dst_slot_id), dst_slot_id);
if(RuleB(QueryServ, PlayerLogMoves)) { QSSwapItemAuditor(move_in, true); } // QS Audit
// Step 8: Re-calc stats
CalcBonuses();
return true;
}
void Client::SwapItemResync(MoveItem_Struct* move_slots) {
// wow..this thing created a helluva memory leak...
// with any luck..this won't be needed in the future
// resync the 'from' and 'to' slots on an as-needed basis
// Not as effective as the full process, but less intrusive to gameplay -U
mlog(INVENTORY__ERROR, "Inventory desyncronization. (charname: %s, source: %i, destination: %i)", GetName(), move_slots->from_slot, move_slots->to_slot);
Message(15, "Inventory Desyncronization detected: Resending slot data...");
if((move_slots->from_slot >= EmuConstants::EQUIPMENT_BEGIN && move_slots->from_slot <= EmuConstants::CURSOR_BAG_END) || move_slots->from_slot == MainPowerSource) {
int16 resync_slot = (Inventory::CalcSlotId(move_slots->from_slot) == INVALID_INDEX) ? move_slots->from_slot : Inventory::CalcSlotId(move_slots->from_slot);
if (IsValidSlot(resync_slot) && resync_slot != INVALID_INDEX) {
// This prevents the client from crashing when closing any 'phantom' bags -U
const Item_Struct* token_struct = database.GetItem(22292); // 'Copper Coin'
ItemInst* token_inst = database.CreateItem(token_struct, 1);
SendItemPacket(resync_slot, token_inst, ItemPacketTrade);
if(m_inv[resync_slot]) { SendItemPacket(resync_slot, m_inv[resync_slot], ItemPacketTrade); }
else {
EQApplicationPacket* outapp = new EQApplicationPacket(OP_DeleteItem, sizeof(DeleteItem_Struct));
DeleteItem_Struct* delete_slot = (DeleteItem_Struct*)outapp->pBuffer;
delete_slot->from_slot = resync_slot;
delete_slot->to_slot = 0xFFFFFFFF;
delete_slot->number_in_stack = 0xFFFFFFFF;
QueuePacket(outapp);
safe_delete(outapp);
}
safe_delete(token_inst);
Message(14, "Source slot %i resyncronized.", move_slots->from_slot);
}
else { Message(13, "Could not resyncronize source slot %i.", move_slots->from_slot); }
}
else {
int16 resync_slot = (Inventory::CalcSlotId(move_slots->from_slot) == INVALID_INDEX) ? move_slots->from_slot : Inventory::CalcSlotId(move_slots->from_slot);
if (IsValidSlot(resync_slot) && resync_slot != INVALID_INDEX) {
if(m_inv[resync_slot]) {
const Item_Struct* token_struct = database.GetItem(22292); // 'Copper Coin'
ItemInst* token_inst = database.CreateItem(token_struct, 1);
SendItemPacket(resync_slot, token_inst, ItemPacketTrade);
SendItemPacket(resync_slot, m_inv[resync_slot], ItemPacketTrade);
safe_delete(token_inst);
Message(14, "Source slot %i resyncronized.", move_slots->from_slot);
}
else { Message(13, "Could not resyncronize source slot %i.", move_slots->from_slot); }
}
else { Message(13, "Could not resyncronize source slot %i.", move_slots->from_slot); }
}
if((move_slots->to_slot >= EmuConstants::EQUIPMENT_BEGIN && move_slots->to_slot <= EmuConstants::CURSOR_BAG_END) || move_slots->to_slot == MainPowerSource) {
int16 resync_slot = (Inventory::CalcSlotId(move_slots->to_slot) == INVALID_INDEX) ? move_slots->to_slot : Inventory::CalcSlotId(move_slots->to_slot);
if (IsValidSlot(resync_slot) && resync_slot != INVALID_INDEX) {
const Item_Struct* token_struct = database.GetItem(22292); // 'Copper Coin'
ItemInst* token_inst = database.CreateItem(token_struct, 1);
SendItemPacket(resync_slot, token_inst, ItemPacketTrade);
if(m_inv[resync_slot]) { SendItemPacket(resync_slot, m_inv[resync_slot], ItemPacketTrade); }
else {
EQApplicationPacket* outapp = new EQApplicationPacket(OP_DeleteItem, sizeof(DeleteItem_Struct));
DeleteItem_Struct* delete_slot = (DeleteItem_Struct*)outapp->pBuffer;
delete_slot->from_slot = resync_slot;
delete_slot->to_slot = 0xFFFFFFFF;
delete_slot->number_in_stack = 0xFFFFFFFF;
QueuePacket(outapp);
safe_delete(outapp);
}
safe_delete(token_inst);
Message(14, "Destination slot %i resyncronized.", move_slots->to_slot);
}
else { Message(13, "Could not resyncronize destination slot %i.", move_slots->to_slot); }
}
else {
int16 resync_slot = (Inventory::CalcSlotId(move_slots->to_slot) == INVALID_INDEX) ? move_slots->to_slot : Inventory::CalcSlotId(move_slots->to_slot);
if (IsValidSlot(resync_slot) && resync_slot != INVALID_INDEX) {
if(m_inv[resync_slot]) {
const Item_Struct* token_struct = database.GetItem(22292); // 'Copper Coin'
ItemInst* token_inst = database.CreateItem(token_struct, 1);
SendItemPacket(resync_slot, token_inst, ItemPacketTrade);
SendItemPacket(resync_slot, m_inv[resync_slot], ItemPacketTrade);
safe_delete(token_inst);
Message(14, "Destination slot %i resyncronized.", move_slots->to_slot);
}
else { Message(13, "Could not resyncronize destination slot %i.", move_slots->to_slot); }
}
else { Message(13, "Could not resyncronize destination slot %i.", move_slots->to_slot); }
}
}
void Client::QSSwapItemAuditor(MoveItem_Struct* move_in, bool postaction_call) {
int16 from_slot_id = static_cast<int16>(move_in->from_slot);
int16 to_slot_id = static_cast<int16>(move_in->to_slot);
int16 move_amount = static_cast<int16>(move_in->number_in_stack);
if(!m_inv[from_slot_id] && !m_inv[to_slot_id]) { return; }
uint16 move_count = 0;
if(m_inv[from_slot_id]) { move_count += m_inv[from_slot_id]->GetTotalItemCount(); }
if(to_slot_id != from_slot_id) { if(m_inv[to_slot_id]) { move_count += m_inv[to_slot_id]->GetTotalItemCount(); } }
ServerPacket* qspack = new ServerPacket(ServerOP_QSPlayerLogMoves, sizeof(QSPlayerLogMove_Struct) + (sizeof(QSMoveItems_Struct) * move_count));
QSPlayerLogMove_Struct* qsaudit = (QSPlayerLogMove_Struct*)qspack->pBuffer;
qsaudit->char_id = character_id;
qsaudit->stack_size = move_amount;
qsaudit->char_count = move_count;
qsaudit->postaction = postaction_call;
qsaudit->from_slot = from_slot_id;
qsaudit->to_slot = to_slot_id;
move_count = 0;
const ItemInst* from_inst = m_inv[postaction_call?to_slot_id:from_slot_id];
if(from_inst) {
qsaudit->items[move_count].from_slot = from_slot_id;
qsaudit->items[move_count].to_slot = to_slot_id;
qsaudit->items[move_count].item_id = from_inst->GetID();
qsaudit->items[move_count].charges = from_inst->GetCharges();
qsaudit->items[move_count].aug_1 = from_inst->GetAugmentItemID(1);
qsaudit->items[move_count].aug_2 = from_inst->GetAugmentItemID(2);
qsaudit->items[move_count].aug_3 = from_inst->GetAugmentItemID(3);
qsaudit->items[move_count].aug_4 = from_inst->GetAugmentItemID(4);
qsaudit->items[move_count++].aug_5 = from_inst->GetAugmentItemID(5);
if(from_inst->IsType(ItemClassContainer)) {
for(uint8 bag_idx = SUB_BEGIN; bag_idx < from_inst->GetItem()->BagSlots; bag_idx++) {
const ItemInst* from_baginst = from_inst->GetItem(bag_idx);
if(from_baginst) {
qsaudit->items[move_count].from_slot = Inventory::CalcSlotId(from_slot_id, bag_idx);
qsaudit->items[move_count].to_slot = Inventory::CalcSlotId(to_slot_id, bag_idx);
qsaudit->items[move_count].item_id = from_baginst->GetID();
qsaudit->items[move_count].charges = from_baginst->GetCharges();
qsaudit->items[move_count].aug_1 = from_baginst->GetAugmentItemID(1);
qsaudit->items[move_count].aug_2 = from_baginst->GetAugmentItemID(2);
qsaudit->items[move_count].aug_3 = from_baginst->GetAugmentItemID(3);
qsaudit->items[move_count].aug_4 = from_baginst->GetAugmentItemID(4);
qsaudit->items[move_count++].aug_5 = from_baginst->GetAugmentItemID(5);
}
}
}
}
if(to_slot_id != from_slot_id) {
const ItemInst* to_inst = m_inv[postaction_call?from_slot_id:to_slot_id];
if(to_inst) {
qsaudit->items[move_count].from_slot = to_slot_id;
qsaudit->items[move_count].to_slot = from_slot_id;
qsaudit->items[move_count].item_id = to_inst->GetID();
qsaudit->items[move_count].charges = to_inst->GetCharges();
qsaudit->items[move_count].aug_1 = to_inst->GetAugmentItemID(1);
qsaudit->items[move_count].aug_2 = to_inst->GetAugmentItemID(2);
qsaudit->items[move_count].aug_3 = to_inst->GetAugmentItemID(3);
qsaudit->items[move_count].aug_4 = to_inst->GetAugmentItemID(4);
qsaudit->items[move_count++].aug_5 = to_inst->GetAugmentItemID(5);
if(to_inst->IsType(ItemClassContainer)) {
for(uint8 bag_idx = SUB_BEGIN; bag_idx < to_inst->GetItem()->BagSlots; bag_idx++) {
const ItemInst* to_baginst = to_inst->GetItem(bag_idx);
if(to_baginst) {
qsaudit->items[move_count].from_slot = Inventory::CalcSlotId(to_slot_id, bag_idx);
qsaudit->items[move_count].to_slot = Inventory::CalcSlotId(from_slot_id, bag_idx);
qsaudit->items[move_count].item_id = to_baginst->GetID();
qsaudit->items[move_count].charges = to_baginst->GetCharges();
qsaudit->items[move_count].aug_1 = to_baginst->GetAugmentItemID(1);
qsaudit->items[move_count].aug_2 = to_baginst->GetAugmentItemID(2);
qsaudit->items[move_count].aug_3 = to_baginst->GetAugmentItemID(3);
qsaudit->items[move_count].aug_4 = to_baginst->GetAugmentItemID(4);
qsaudit->items[move_count++].aug_5 = to_baginst->GetAugmentItemID(5);
}
}
}
}
}
if(move_count && worldserver.Connected()) {
qspack->Deflate();
worldserver.SendPacket(qspack);
}
safe_delete(qspack);
}
void Client::DyeArmor(DyeStruct* dye){
int16 slot=0;
for (int i = EmuConstants::MATERIAL_BEGIN; i <= EmuConstants::MATERIAL_TINT_END; i++) {
if(m_pp.item_tint[i].rgb.blue!=dye->dye[i].rgb.blue ||
m_pp.item_tint[i].rgb.red!=dye->dye[i].rgb.red ||
m_pp.item_tint[i].rgb.green != dye->dye[i].rgb.green){
slot = m_inv.HasItem(32557, 1, invWherePersonal);
if (slot != INVALID_INDEX){
DeleteItemInInventory(slot,1,true);
uint8 slot2=SlotConvert(i);
ItemInst* inst = this->m_inv.GetItem(slot2);
if(inst){
inst->SetColor((dye->dye[i].rgb.red*65536)+(dye->dye[i].rgb.green*256)+(dye->dye[i].rgb.blue));
database.SaveInventory(CharacterID(),inst,slot2);
if(dye->dye[i].rgb.use_tint)
m_pp.item_tint[i].rgb.use_tint = 0xFF;
else
m_pp.item_tint[i].rgb.use_tint=0x00;
}
m_pp.item_tint[i].rgb.blue=dye->dye[i].rgb.blue;
m_pp.item_tint[i].rgb.red=dye->dye[i].rgb.red;
m_pp.item_tint[i].rgb.green=dye->dye[i].rgb.green;
SendWearChange(i);
}
else{
Message(13,"Could not locate A Vial of Prismatic Dye.");
return;
}
}
}
EQApplicationPacket* outapp=new EQApplicationPacket(OP_Dye,0);
QueuePacket(outapp);
safe_delete(outapp);
Save();
}
/*bool Client::DecreaseByItemType(uint32 type, uint8 amt) {
const Item_Struct* TempItem = 0;
ItemInst* ins;
int x;
for(x=EmuConstants::POSSESSIONS_BEGIN; x <= EmuConstants::POSSESSIONS_END; x++)
{
TempItem = 0;
ins = GetInv().GetItem(x);
if (ins)
TempItem = ins->GetItem();
if (TempItem && TempItem->ItemType == type)
{
if (ins->GetCharges() < amt)
{
amt -= ins->GetCharges();
DeleteItemInInventory(x,amt,true);
}
else
{
DeleteItemInInventory(x,amt,true);
amt = 0;
}
if (amt < 1)
return true;
}
}
for(x=EmuConstants::GENERAL_BAGS_BEGIN; x <= EmuConstants::GENERAL_BAGS_END; x++)
{
TempItem = 0;
ins = GetInv().GetItem(x);
if (ins)
TempItem = ins->GetItem();
if (TempItem && TempItem->ItemType == type)
{
if (ins->GetCharges() < amt)
{
amt -= ins->GetCharges();
DeleteItemInInventory(x,amt,true);
}
else
{
DeleteItemInInventory(x,amt,true);
amt = 0;
}
if (amt < 1)
return true;
}
}
return false;
}*/
bool Client::DecreaseByID(uint32 type, uint8 amt) {
const Item_Struct* TempItem = 0;
ItemInst* ins;
int x;
int num = 0;
for(x = EmuConstants::EQUIPMENT_BEGIN; x <= EmuConstants::GENERAL_BAGS_END; x++)
{
if (x == MainCursor + 1)
x = EmuConstants::GENERAL_BAGS_BEGIN;
TempItem = 0;
ins = GetInv().GetItem(x);
if (ins)
TempItem = ins->GetItem();
if (TempItem && TempItem->ID == type)
{
num += ins->GetCharges();
if (num >= amt)
break;
}
}
if (num < amt)
return false;
for(x = EmuConstants::EQUIPMENT_BEGIN; x <= EmuConstants::GENERAL_BAGS_END; x++) // should this be CURSOR_BAG_END?
{
if (x == MainCursor + 1)
x = EmuConstants::GENERAL_BAGS_BEGIN;
TempItem = 0;
ins = GetInv().GetItem(x);
if (ins)
TempItem = ins->GetItem();
if (TempItem && TempItem->ID == type)
{
if (ins->GetCharges() < amt)
{
amt -= ins->GetCharges();
DeleteItemInInventory(x,amt,true);
}
else
{
DeleteItemInInventory(x,amt,true);
amt = 0;
}
if (amt < 1)
break;
}
}
return true;
}
void Client::RemoveNoRent(bool client_update) {
int16 slot_id;
// personal
for(slot_id = MAIN_BEGIN; slot_id < EmuConstants::MAP_POSSESSIONS_SIZE; slot_id++) {
const ItemInst* inst = m_inv[slot_id];
if(inst && !inst->GetItem()->NoRent) {
mlog(INVENTORY__SLOTS, "NoRent Timer Lapse: Deleting %s from slot %i", inst->GetItem()->Name, slot_id);
DeleteItemInInventory(slot_id, 0, client_update);
}
}
// power source
const ItemInst* inst = m_inv[MainPowerSource];
if(inst && !inst->GetItem()->NoRent) {
mlog(INVENTORY__SLOTS, "NoRent Timer Lapse: Deleting %s from slot %i", inst->GetItem()->Name, MainPowerSource);
DeleteItemInInventory(MainPowerSource, 0, (GetClientVersion() >= EQClientSoF) ? client_update : false); // Ti slot non-existent
}
// containers
for(slot_id = EmuConstants::GENERAL_BAGS_BEGIN; slot_id <= EmuConstants::CURSOR_BAG_END; slot_id++) {
const ItemInst* inst = m_inv[slot_id];
if(inst && !inst->GetItem()->NoRent) {
mlog(INVENTORY__SLOTS, "NoRent Timer Lapse: Deleting %s from slot %i", inst->GetItem()->Name, slot_id);
DeleteItemInInventory(slot_id, 0, client_update);
}
}
// bank
for(slot_id = EmuConstants::BANK_BEGIN; slot_id <= EmuConstants::BANK_END; slot_id++) {
const ItemInst* inst = m_inv[slot_id];
if(inst && !inst->GetItem()->NoRent) {
mlog(INVENTORY__SLOTS, "NoRent Timer Lapse: Deleting %s from slot %i", inst->GetItem()->Name, slot_id);
DeleteItemInInventory(slot_id, 0, false); // Can't delete from client Bank slots
}
}
// bank containers
for(slot_id = EmuConstants::BANK_BAGS_BEGIN; slot_id <= EmuConstants::BANK_BAGS_END; slot_id++) {
const ItemInst* inst = m_inv[slot_id];
if(inst && !inst->GetItem()->NoRent) {
mlog(INVENTORY__SLOTS, "NoRent Timer Lapse: Deleting %s from slot %i", inst->GetItem()->Name, slot_id);
DeleteItemInInventory(slot_id, 0, false); // Can't delete from client Bank Container slots
}
}
// shared bank
for(slot_id = EmuConstants::SHARED_BANK_BEGIN; slot_id <= EmuConstants::SHARED_BANK_END; slot_id++) {
const ItemInst* inst = m_inv[slot_id];
if(inst && !inst->GetItem()->NoRent) {
mlog(INVENTORY__SLOTS, "NoRent Timer Lapse: Deleting %s from slot %i", inst->GetItem()->Name, slot_id);
DeleteItemInInventory(slot_id, 0, false); // Can't delete from client Shared Bank slots
}
}
// shared bank containers
for(slot_id = EmuConstants::SHARED_BANK_BAGS_BEGIN; slot_id <= EmuConstants::SHARED_BANK_BAGS_END; slot_id++) {
const ItemInst* inst = m_inv[slot_id];
if(inst && !inst->GetItem()->NoRent) {
mlog(INVENTORY__SLOTS, "NoRent Timer Lapse: Deleting %s from slot %i", inst->GetItem()->Name, slot_id);
DeleteItemInInventory(slot_id, 0, false); // Can't delete from client Shared Bank Container slots
}
}
}
// Two new methods to alleviate perpetual login desyncs
void Client::RemoveDuplicateLore(bool client_update) {
// Split-charge stacking may be added at some point -U
int16 slot_id;
// personal
for(slot_id = MAIN_BEGIN; slot_id < EmuConstants::MAP_POSSESSIONS_SIZE; slot_id++) {
ItemInst* inst = m_inv.PopItem(slot_id);
if(inst) {
if(CheckLoreConflict(inst->GetItem())) {
mlog(INVENTORY__ERROR, "Lore Duplication Error: Deleting %s from slot %i", inst->GetItem()->Name, slot_id);
database.SaveInventory(character_id, nullptr, slot_id);
}
else {
m_inv.PutItem(slot_id, *inst);
}
safe_delete(inst);
}
}
// power source
ItemInst* inst = m_inv.PopItem(MainPowerSource);
if(inst) {
if(CheckLoreConflict(inst->GetItem())) {
mlog(INVENTORY__ERROR, "Lore Duplication Error: Deleting %s from slot %i", inst->GetItem()->Name, slot_id);
database.SaveInventory(character_id, nullptr, MainPowerSource);
}
else {
m_inv.PutItem(MainPowerSource, *inst);
}
safe_delete(inst);
}
// containers
for(slot_id = EmuConstants::GENERAL_BAGS_BEGIN; slot_id <= EmuConstants::CURSOR_BAG_END; slot_id++) {
ItemInst* inst = m_inv.PopItem(slot_id);
if(inst) {
if(CheckLoreConflict(inst->GetItem())) {
mlog(INVENTORY__ERROR, "Lore Duplication Error: Deleting %s from slot %i", inst->GetItem()->Name, slot_id);
database.SaveInventory(character_id, nullptr, slot_id);
}
else {
m_inv.PutItem(slot_id, *inst);
}
safe_delete(inst);
}
}
// bank
for(slot_id = EmuConstants::BANK_BEGIN; slot_id <= EmuConstants::BANK_END; slot_id++) {
ItemInst* inst = m_inv.PopItem(slot_id);
if(inst) {
if(CheckLoreConflict(inst->GetItem())) {
mlog(INVENTORY__ERROR, "Lore Duplication Error: Deleting %s from slot %i", inst->GetItem()->Name, slot_id);
database.SaveInventory(character_id, nullptr, slot_id);
}
else {
m_inv.PutItem(slot_id, *inst);
}
safe_delete(inst);
}
}
// bank containers
for(slot_id = EmuConstants::BANK_BAGS_BEGIN; slot_id <= EmuConstants::BANK_BAGS_END; slot_id++) {
ItemInst* inst = m_inv.PopItem(slot_id);
if(inst) {
if(CheckLoreConflict(inst->GetItem())) {
mlog(INVENTORY__ERROR, "Lore Duplication Error: Deleting %s from slot %i", inst->GetItem()->Name, slot_id);
database.SaveInventory(character_id, nullptr, slot_id);
}
else {
m_inv.PutItem(slot_id, *inst);
}
safe_delete(inst);
}
}
// Shared Bank and Shared Bank Containers are not checked due to their allowing duplicate lore items -U
}
void Client::MoveSlotNotAllowed(bool client_update) {
int16 slot_id;
// equipment
for(slot_id = EmuConstants::EQUIPMENT_BEGIN; slot_id <= EmuConstants::EQUIPMENT_END; slot_id++) {
if(m_inv[slot_id] && !m_inv[slot_id]->IsSlotAllowed(slot_id)) {
ItemInst* inst = m_inv.PopItem(slot_id);
bool is_arrow = (inst->GetItem()->ItemType == ItemTypeArrow) ? true : false;
int16 free_slot_id = m_inv.FindFreeSlot(inst->IsType(ItemClassContainer), true, inst->GetItem()->Size, is_arrow);
mlog(INVENTORY__ERROR, "Slot Assignment Error: Moving %s from slot %i to %i", inst->GetItem()->Name, slot_id, free_slot_id);
PutItemInInventory(free_slot_id, *inst, client_update);
database.SaveInventory(character_id, nullptr, slot_id);
safe_delete(inst);
}
}
// power source
slot_id = MainPowerSource;
if(m_inv[slot_id] && !m_inv[slot_id]->IsSlotAllowed(slot_id)) {
ItemInst* inst = m_inv.PopItem(slot_id);
bool is_arrow = (inst->GetItem()->ItemType == ItemTypeArrow) ? true : false;
int16 free_slot_id = m_inv.FindFreeSlot(inst->IsType(ItemClassContainer), true, inst->GetItem()->Size, is_arrow);
mlog(INVENTORY__ERROR, "Slot Assignment Error: Moving %s from slot %i to %i", inst->GetItem()->Name, slot_id, free_slot_id);
PutItemInInventory(free_slot_id, *inst, (GetClientVersion() >= EQClientSoF) ? client_update : false);
database.SaveInventory(character_id, nullptr, slot_id);
safe_delete(inst);
}
// No need to check inventory, cursor, bank or shared bank since they allow max item size and containers -U
// Code can be added to check item size vs. container size, but it is left to attrition for now.
}
// these functions operate with a material slot, which is from 0 to 8
uint32 Client::GetEquipment(uint8 material_slot) const
{
int invslot;
const ItemInst *item;
if(material_slot > EmuConstants::MATERIAL_END)
{
return 0;
}
invslot = Inventory::CalcSlotFromMaterial(material_slot);
if(invslot == -1)
{
return 0;
}
item = m_inv.GetItem(invslot);
if(item && item->GetItem())
{
return item->GetItem()->ID;
}
return 0;
}
/*
int32 Client::GetEquipmentMaterial(uint8 material_slot)
{
const Item_Struct *item;
item = database.GetItem(GetEquipment(material_slot));
if(item != 0)
{
return item->Material;
}
return 0;
}
*/
uint32 Client::GetEquipmentColor(uint8 material_slot) const
{
const Item_Struct *item;
if(material_slot > EmuConstants::MATERIAL_END)
{
return 0;
}
item = database.GetItem(GetEquipment(material_slot));
if(item != 0)
{
return m_pp.item_tint[material_slot].rgb.use_tint ?
m_pp.item_tint[material_slot].color :
item->Color;
}
return 0;
}
// Send an item packet (including all subitems of the item)
void Client::SendItemPacket(int16 slot_id, const ItemInst* inst, ItemPacketType packet_type)
{
if (!inst)
return;
// Serialize item into |-delimited string
std::string packet = inst->Serialize(slot_id);
EmuOpcode opcode = OP_Unknown;
EQApplicationPacket* outapp = nullptr;
ItemPacket_Struct* itempacket = nullptr;
// Construct packet
opcode = (packet_type==ItemPacketViewLink) ? OP_ItemLinkResponse : OP_ItemPacket;
outapp = new EQApplicationPacket(opcode, packet.length()+sizeof(ItemPacket_Struct));
itempacket = (ItemPacket_Struct*)outapp->pBuffer;
memcpy(itempacket->SerializedItem, packet.c_str(), packet.length());
itempacket->PacketType = packet_type;
#if EQDEBUG >= 9
DumpPacket(outapp);
#endif
FastQueuePacket(&outapp);
}
EQApplicationPacket* Client::ReturnItemPacket(int16 slot_id, const ItemInst* inst, ItemPacketType packet_type)
{
if (!inst)
return 0;
// Serialize item into |-delimited string
std::string packet = inst->Serialize(slot_id);
EmuOpcode opcode = OP_Unknown;
EQApplicationPacket* outapp = nullptr;
BulkItemPacket_Struct* itempacket = nullptr;
// Construct packet
opcode = OP_ItemPacket;
outapp = new EQApplicationPacket(opcode, packet.length()+1);
itempacket = (BulkItemPacket_Struct*)outapp->pBuffer;
memcpy(itempacket->SerializedItem, packet.c_str(), packet.length());
#if EQDEBUG >= 9
DumpPacket(outapp);
#endif
return outapp;
}
static int16 BandolierSlotToWeaponSlot(int BandolierSlot) {
switch(BandolierSlot) {
case bandolierMainHand:
return MainPrimary;
case bandolierOffHand:
return MainSecondary;
case bandolierRange:
return MainRange;
default:
return MainAmmo;
}
}
void Client::CreateBandolier(const EQApplicationPacket *app) {
// Store bandolier set with the number and name passed by the client, along with the items that are currently
// in the players weapon slots.
BandolierCreate_Struct *bs = (BandolierCreate_Struct*)app->pBuffer;
_log(INVENTORY__BANDOLIER, "Char: %s Creating Bandolier Set %i, Set Name: %s", GetName(), bs->number, bs->name);
strcpy(m_pp.bandoliers[bs->number].name, bs->name);
const ItemInst* InvItem;
const Item_Struct *BaseItem;
int16 WeaponSlot;
for(int BandolierSlot = bandolierMainHand; BandolierSlot <= bandolierAmmo; BandolierSlot++) {
WeaponSlot = BandolierSlotToWeaponSlot(BandolierSlot);
InvItem = GetInv()[WeaponSlot];
if(InvItem) {
BaseItem = InvItem->GetItem();
_log(INVENTORY__BANDOLIER, "Char: %s adding item %s to slot %i", GetName(),BaseItem->Name, WeaponSlot);
m_pp.bandoliers[bs->number].items[BandolierSlot].item_id = BaseItem->ID;
m_pp.bandoliers[bs->number].items[BandolierSlot].icon = BaseItem->Icon;
}
else {
_log(INVENTORY__BANDOLIER, "Char: %s no item in slot %i", GetName(), WeaponSlot);
m_pp.bandoliers[bs->number].items[BandolierSlot].item_id = 0;
m_pp.bandoliers[bs->number].items[BandolierSlot].icon = 0;
}
}
Save();
}
void Client::RemoveBandolier(const EQApplicationPacket *app) {
// Delete bandolier with the specified number
BandolierDelete_Struct *bds = (BandolierDelete_Struct*)app->pBuffer;
_log(INVENTORY__BANDOLIER, "Char: %s removing set", GetName(), bds->number);
memset(m_pp.bandoliers[bds->number].name, 0, 32);
for(int i = bandolierMainHand; i <= bandolierAmmo; i++) {
m_pp.bandoliers[bds->number].items[i].item_id = 0;
m_pp.bandoliers[bds->number].items[i].icon = 0;
}
Save();
}
void Client::SetBandolier(const EQApplicationPacket *app) {
// Swap the weapons in the given bandolier set into the character's weapon slots and return
// any items currently in the weapon slots to inventory.
BandolierSet_Struct *bss = (BandolierSet_Struct*)app->pBuffer;
_log(INVENTORY__BANDOLIER, "Char: %s activating set %i", GetName(), bss->number);
int16 slot;
int16 WeaponSlot;
ItemInst *BandolierItems[4]; // Temporary holding area for the weapons we pull out of their inventory
// First we pull the items for this bandolier set out of their inventory, this makes space to put the
// currently equipped items back.
for(int BandolierSlot = bandolierMainHand; BandolierSlot <= bandolierAmmo; BandolierSlot++) {
// If this bandolier set has an item in this position
if(m_pp.bandoliers[bss->number].items[BandolierSlot].item_id) {
WeaponSlot = BandolierSlotToWeaponSlot(BandolierSlot);
// Check if the player has the item specified in the bandolier set on them.
//
slot = m_inv.HasItem(m_pp.bandoliers[bss->number].items[BandolierSlot].item_id, 1,
invWhereWorn|invWherePersonal);
// removed 'invWhereCursor' argument from above and implemented slots 30, 331-340 checks here
if (slot == INVALID_INDEX) {
if (m_inv.GetItem(MainCursor)) {
if (m_inv.GetItem(MainCursor)->GetItem()->ID == m_pp.bandoliers[bss->number].items[BandolierSlot].item_id &&
m_inv.GetItem(MainCursor)->GetCharges() >= 1) // '> 0' the same, but this matches Inventory::_HasItem conditional check
slot = MainCursor;
else if (m_inv.GetItem(MainCursor)->GetItem()->ItemClass == 1) {
for(int16 CursorBagSlot = EmuConstants::CURSOR_BAG_BEGIN; CursorBagSlot <= EmuConstants::CURSOR_BAG_END; CursorBagSlot++) {
if (m_inv.GetItem(CursorBagSlot)) {
if (m_inv.GetItem(CursorBagSlot)->GetItem()->ID == m_pp.bandoliers[bss->number].items[BandolierSlot].item_id &&
m_inv.GetItem(CursorBagSlot)->GetCharges() >= 1) { // ditto
slot = CursorBagSlot;
break;
}
}
}
}
}
}
// if the player has this item in their inventory,
if (slot != INVALID_INDEX) {
// Pull the item out of the inventory
BandolierItems[BandolierSlot] = m_inv.PopItem(slot);
// If ammo with charges, only take one charge out to put in the range slot, that is what
// the client does.
if(((BandolierSlot == bandolierAmmo) || (BandolierSlot == bandolierRange)) &&
BandolierItems[BandolierSlot] && BandolierItems[BandolierSlot]->IsStackable()){
int Charges = BandolierItems[BandolierSlot]->GetCharges();
// If there is more than one charge
if(Charges > 1) {
BandolierItems[BandolierSlot]->SetCharges(Charges-1);
// Take one charge out and put the rest back
m_inv.PutItem(slot, *BandolierItems[BandolierSlot]);
database.SaveInventory(character_id, BandolierItems[BandolierSlot], slot);
BandolierItems[BandolierSlot]->SetCharges(1);
}
else // Remove the item from the inventory
database.SaveInventory(character_id, 0, slot);
}
else // Remove the item from the inventory
database.SaveInventory(character_id, 0, slot);
}
else { // The player doesn't have the required weapon with them.
BandolierItems[BandolierSlot] = 0;
if (slot == INVALID_INDEX) {
_log(INVENTORY__BANDOLIER, "Character does not have required bandolier item for slot %i", WeaponSlot);
ItemInst *InvItem = m_inv.PopItem(WeaponSlot);
if(InvItem) {
// If there was an item in that weapon slot, put it in the inventory
_log(INVENTORY__BANDOLIER, "returning item %s in weapon slot %i to inventory",
InvItem->GetItem()->Name, WeaponSlot);
if(MoveItemToInventory(InvItem))
database.SaveInventory(character_id, 0, WeaponSlot);
else
_log(INVENTORY__BANDOLIER, "Char: %s, ERROR returning %s to inventory", GetName(),
InvItem->GetItem()->Name);
safe_delete(InvItem);
}
}
}
}
}
// Now we move the required weapons into the character weapon slots, and return any items we are replacing
// back to inventory.
//
for(int BandolierSlot = bandolierMainHand; BandolierSlot <= bandolierAmmo; BandolierSlot++) {
// Find the inventory slot corresponding to this bandolier slot
WeaponSlot = BandolierSlotToWeaponSlot(BandolierSlot);
// if there is an item in this Bandolier slot ?
if(m_pp.bandoliers[bss->number].items[BandolierSlot].item_id) {
// if the player has this item in their inventory, and it is not already where it needs to be
if(BandolierItems[BandolierSlot]) {
// Pull the item that we are going to replace
ItemInst *InvItem = m_inv.PopItem(WeaponSlot);
// Put the item specified in the bandolier where it needs to be
m_inv.PutItem(WeaponSlot, *BandolierItems[BandolierSlot]);
safe_delete(BandolierItems[BandolierSlot]);
// Update the database, save the item now in the weapon slot
database.SaveInventory(character_id, m_inv.GetItem(WeaponSlot), WeaponSlot);
if(InvItem) {
// If there was already an item in that weapon slot that we replaced, find a place to put it
if(!MoveItemToInventory(InvItem))
_log(INVENTORY__BANDOLIER, "Char: %s, ERROR returning %s to inventory", GetName(),
InvItem->GetItem()->Name);
safe_delete(InvItem);
}
}
}
else {
// This bandolier set has no item for this slot, so take whatever is in the weapon slot and
// put it in the player's inventory.
ItemInst *InvItem = m_inv.PopItem(WeaponSlot);
if(InvItem) {
_log(INVENTORY__BANDOLIER, "Bandolier has no item for slot %i, returning item %s to inventory",
WeaponSlot, InvItem->GetItem()->Name);
// If there was an item in that weapon slot, put it in the inventory
if(MoveItemToInventory(InvItem))
database.SaveInventory(character_id, 0, WeaponSlot);
else
_log(INVENTORY__BANDOLIER, "Char: %s, ERROR returning %s to inventory", GetName(),
InvItem->GetItem()->Name);
safe_delete(InvItem);
}
}
}
// finally, recalculate any stat bonuses from the item change
CalcBonuses();
}
bool Client::MoveItemToInventory(ItemInst *ItemToReturn, bool UpdateClient) {
// This is a support function for Client::SetBandolier, however it can be used anywhere it's functionality is required.
//
// When the client moves items around as Bandolier sets are activated, it does not send details to the
// server of what item it has moved to which slot. It assumes the server knows what it will do.
//
// The standard EQEmu auto inventory routines do not behave as the client does when manipulating bandoliers.
// The client will look in each main inventory slot. If it finds a bag in a slot, it will then look inside
// the bag for a free slot.
//
// This differs from the standard EQEmu method of looking in all 8 inventory slots first to find an empty slot, and
// then going back and looking in bags. There are also other differences related to how it moves stackable items back
// to inventory.
//
// Rather than alter the current auto inventory behaviour, just in case something
// depends on current behaviour, this routine operates the same as the client when moving items back to inventory when
// swapping bandolier sets.
if(!ItemToReturn) return false;
_log(INVENTORY__SLOTS,"Char: %s Returning %s to inventory", GetName(), ItemToReturn->GetItem()->Name);
uint32 ItemID = ItemToReturn->GetItem()->ID;
// If the item is stackable (ammo in range slot), try stacking it with other items of the same type
//
if(ItemToReturn->IsStackable()) {
for (int16 i = EmuConstants::GENERAL_BEGIN; i <= MainCursor; i++) { // changed slot max to 30 from 29. client will stack into slot 30 (bags too) before moving.
ItemInst* InvItem = m_inv.GetItem(i);
if(InvItem && (InvItem->GetItem()->ID == ItemID) && (InvItem->GetCharges() < InvItem->GetItem()->StackSize)) {
int ChargeSlotsLeft = InvItem->GetItem()->StackSize - InvItem->GetCharges();
int ChargesToMove = ItemToReturn->GetCharges() < ChargeSlotsLeft ? ItemToReturn->GetCharges() :
ChargeSlotsLeft;
InvItem->SetCharges(InvItem->GetCharges() + ChargesToMove);
if(UpdateClient)
SendItemPacket(i, InvItem, ItemPacketTrade);
database.SaveInventory(character_id, m_inv.GetItem(i), i);
ItemToReturn->SetCharges(ItemToReturn->GetCharges() - ChargesToMove);
if(!ItemToReturn->GetCharges())
return true;
}
// If there is a bag in this slot, look inside it.
//
if (InvItem && InvItem->IsType(ItemClassContainer)) {
int16 BaseSlotID = Inventory::CalcSlotId(i, SUB_BEGIN);
uint8 BagSize=InvItem->GetItem()->BagSlots;
uint8 BagSlot;
for (BagSlot = SUB_BEGIN; BagSlot < BagSize; BagSlot++) {
InvItem = m_inv.GetItem(BaseSlotID + BagSlot);
if (InvItem && (InvItem->GetItem()->ID == ItemID) &&
(InvItem->GetCharges() < InvItem->GetItem()->StackSize)) {
int ChargeSlotsLeft = InvItem->GetItem()->StackSize - InvItem->GetCharges();
int ChargesToMove = ItemToReturn->GetCharges() < ChargeSlotsLeft
? ItemToReturn->GetCharges() : ChargeSlotsLeft;
InvItem->SetCharges(InvItem->GetCharges() + ChargesToMove);
if(UpdateClient)
SendItemPacket(BaseSlotID + BagSlot, m_inv.GetItem(BaseSlotID + BagSlot),
ItemPacketTrade);
database.SaveInventory(character_id, m_inv.GetItem(BaseSlotID + BagSlot),
BaseSlotID + BagSlot);
ItemToReturn->SetCharges(ItemToReturn->GetCharges() - ChargesToMove);
if(!ItemToReturn->GetCharges())
return true;
}
}
}
}
}
// We have tried stacking items, now just try and find an empty slot.
for (int16 i = EmuConstants::GENERAL_BEGIN; i <= MainCursor; i++) { // changed slot max to 30 from 29. client will move into slot 30 (bags too) before pushing onto cursor.
ItemInst* InvItem = m_inv.GetItem(i);
if (!InvItem) {
// Found available slot in personal inventory
m_inv.PutItem(i, *ItemToReturn);
if(UpdateClient)
SendItemPacket(i, ItemToReturn, ItemPacketTrade);
database.SaveInventory(character_id, m_inv.GetItem(i), i);
_log(INVENTORY__SLOTS, "Char: %s Storing in main inventory slot %i", GetName(), i);
return true;
}
if(InvItem->IsType(ItemClassContainer) && Inventory::CanItemFitInContainer(ItemToReturn->GetItem(), InvItem->GetItem())) {
int16 BaseSlotID = Inventory::CalcSlotId(i, SUB_BEGIN);
uint8 BagSize=InvItem->GetItem()->BagSlots;
for (uint8 BagSlot = SUB_BEGIN; BagSlot < BagSize; BagSlot++) {
InvItem = m_inv.GetItem(BaseSlotID + BagSlot);
if (!InvItem) {
// Found available slot within bag
m_inv.PutItem(BaseSlotID + BagSlot, *ItemToReturn);
if(UpdateClient)
SendItemPacket(BaseSlotID + BagSlot, ItemToReturn, ItemPacketTrade);
database.SaveInventory(character_id, m_inv.GetItem(BaseSlotID + BagSlot), BaseSlotID + BagSlot);
_log(INVENTORY__SLOTS, "Char: %s Storing in bag slot %i", GetName(), BaseSlotID + BagSlot);
return true;
}
}
}
}
// Store on the cursor
//
_log(INVENTORY__SLOTS, "Char: %s No space, putting on the cursor", GetName());
PushItemOnCursor(*ItemToReturn, UpdateClient);
return true;
}
void Inventory::SetCustomItemData(uint32 character_id, int16 slot_id, std::string identifier, std::string value) {
ItemInst *inst = GetItem(slot_id);
if(inst) {
inst->SetCustomData(identifier, value);
database.SaveInventory(character_id, inst, slot_id);
}
}
void Inventory::SetCustomItemData(uint32 character_id, int16 slot_id, std::string identifier, int value) {
ItemInst *inst = GetItem(slot_id);
if(inst) {
inst->SetCustomData(identifier, value);
database.SaveInventory(character_id, inst, slot_id);
}
}
void Inventory::SetCustomItemData(uint32 character_id, int16 slot_id, std::string identifier, float value) {
ItemInst *inst = GetItem(slot_id);
if(inst) {
inst->SetCustomData(identifier, value);
database.SaveInventory(character_id, inst, slot_id);
}
}
void Inventory::SetCustomItemData(uint32 character_id, int16 slot_id, std::string identifier, bool value) {
ItemInst *inst = GetItem(slot_id);
if(inst) {
inst->SetCustomData(identifier, value);
database.SaveInventory(character_id, inst, slot_id);
}
}
std::string Inventory::GetCustomItemData(int16 slot_id, std::string identifier) {
ItemInst *inst = GetItem(slot_id);
if(inst) {
return inst->GetCustomData(identifier);
}
return "";
}