/*	EQEMu: Everquest Server Emulator
	Copyright (C) 2001-2016 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/global_define.h"
#include "../common/eqemu_logsys.h"

#include "../common/strings.h"
#include "quest_parser_collection.h"
#include "worldserver.h"
#include "zonedb.h"
#include "../common/events/player_event_logs.h"
#include "bot.h"
#include "../common/evolving_items.h"
#include "../common/repositories/character_corpse_items_repository.h"
#include "queryserv.h"

extern WorldServer worldserver;
extern QueryServ  *QServ;

// @merth: this needs to be touched up
uint32 Client::NukeItem(uint32 itemnum, uint8 where_to_check) {
	if (itemnum == 0)
		return 0;
	uint32 x = 0;
	EQ::ItemInstance *cur = nullptr;

	int i;
	if(where_to_check & invWhereWorn) {
		for (i = EQ::invslot::EQUIPMENT_BEGIN; i <= EQ::invslot::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, ((((uint64)1 << i) & GetInv().GetLookup()->PossessionsBitmask) != 0));
			}
		}
	}

	if(where_to_check & invWhereCursor) {
		if (GetItemIDAt(EQ::invslot::slotCursor) == itemnum || (itemnum == 0xFFFE && GetItemIDAt(EQ::invslot::slotCursor) != INVALID_ID)) {
			cur = m_inv.GetItem(EQ::invslot::slotCursor);
			if(cur && cur->GetItem()->Stackable) {
				x += cur->GetCharges();
			} else {
				x++;
			}

			DeleteItemInInventory(EQ::invslot::slotCursor, 0, true);
		}

		for (i = EQ::invbag::CURSOR_BAG_BEGIN; i <= EQ::invbag::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 = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::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, ((((uint64)1 << i) & GetInv().GetLookup()->PossessionsBitmask) != 0));
			}
		}

		for (i = EQ::invbag::GENERAL_BAGS_BEGIN; i <= EQ::invbag::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, ((((uint64)1 << (EQ::invslot::GENERAL_BEGIN + ((i - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT))) & GetInv().GetLookup()->PossessionsBitmask) != 0));
			}
		}
	}

	if(where_to_check & invWhereBank) {
		for (i = EQ::invslot::BANK_BEGIN; i <= EQ::invslot::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, ((i - EQ::invslot::BANK_BEGIN) >= GetInv().GetLookup()->InventoryTypeSize.Bank));
			}
		}

		for (i = EQ::invbag::BANK_BAGS_BEGIN; i <= EQ::invbag::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, (((i - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT) >= GetInv().GetLookup()->InventoryTypeSize.Bank));
			}
		}
	}

	if(where_to_check & invWhereSharedBank) {
		for (i = EQ::invslot::SHARED_BANK_BEGIN; i <= EQ::invslot::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 = EQ::invbag::SHARED_BANK_BAGS_BEGIN; i <= EQ::invbag::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 EQ::ItemData* item)
{
	if (!item) { return false; }
	if (!item->LoreFlag) { return false; }
	if (item->LoreGroup == 0) { 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, uint32 aug6, bool attuned, uint16 to_slot, uint32 ornament_icon, uint32 ornament_idfile, uint32 ornament_hero_model) {
	const EQ::ItemData* item = database.GetItem(item_id);

	// make sure the item exists
	if(item == nullptr) {
		Message(
			Chat::Red,
			fmt::format(
				"Item {} does not exist.",
				item_id
			).c_str()
		);
		LogInventory(
			"Player [{}] on account [{}] attempted to create an item with an invalid id.\n"
			"Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n",
			GetName(),
			account_name,
			item_id,
			aug1,
			aug2,
			aug3,
			aug4,
			aug5,
			aug6
		);
		return false;
	}
	// check that there is not a lore conflict between base item and existing inventory
	else if(CheckLoreConflict(item)) {
		// DuplicateLoreMessage(item_id);
		Message(
			Chat::Red,
			fmt::format(
				"You already have a lore {} ({}) in your inventory.",
				database.CreateItemLink(item_id),
				item_id
			).c_str()
		);
		return false;
	}
	// check to make sure we are augmenting an augmentable item
	else if (((!item->IsClassCommon()) || (item->AugType > 0)) && (aug1 | aug2 | aug3 | aug4 | aug5 | aug6)) {
		Message(Chat::Red, "You can not augment an augment or a non-common class item.");
		LogInventory(
			"Player [{}] on account [{}] attempted to augment an augment or a non-common class item.\n"
			"Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug5: [{}])\n",
			GetName(),
			account_name,
			item->ID,
			aug1,
			aug2,
			aug3,
			aug4,
			aug5,
			aug6
		);
		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 && ((Admin() < item->MinStatus) || (Admin() < RuleI(GM, MinStatusToSummonItem)))) {
		Message(Chat::Red, "You are not a GM or do not have the status to summon this item.");
		LogInventory("Player [{}] on account [{}] attempted to create a GM-only item with a status of [{}].\n"Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}], MinStatus: [{}])\n",
			GetName(), account_name, Admin(), item->ID, aug1, aug2, aug3, aug4, aug5, aug6, item->MinStatus);

		return false;
	}
	*/

	uint32 augments[EQ::invaug::SOCKET_COUNT] = { aug1, aug2, aug3, aug4, aug5, aug6 };
	uint32 classes = item->Classes;
	uint32 races = item->Races;
	uint32 slots = item->Slots;
	bool enforce_wearable = RuleB(Inventory, EnforceAugmentWear);
	bool enforce_restrictions = RuleB(Inventory, EnforceAugmentRestriction);
	bool enforce_usable = RuleB(Inventory, EnforceAugmentUsability);
	for (int iter = EQ::invaug::SOCKET_BEGIN; iter <= EQ::invaug::SOCKET_END; ++iter) {
		int augment_slot = iter + 1;
		const EQ::ItemData* augtest = database.GetItem(augments[iter]);
		if(augtest == nullptr) {
			if(augments[iter]) {
				Message(
					Chat::Red,
					fmt::format(
						"Augment {} in Augment Slot {} does not exist.",
						augments[iter],
						augment_slot
					).c_str()
				);
				LogInventory(
					"Player [{}] on account [{}] attempted to create an augment (Aug[{}]) with an invalid id.\n"
					"Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n",
					GetName(),
					account_name,
					augment_slot,
					item->ID,
					aug1,
					aug2,
					aug3,
					aug4,
					aug5,
					aug6
				);

				return false;
			}
		}
		else {
			// check that there is not a lore conflict between augment and existing inventory
			if(CheckLoreConflict(augtest)) {
				// DuplicateLoreMessage(augtest->ID);
				Message(
					Chat::Red,
					fmt::format(
						"You already have a lore {} ({}) in your inventory.",
						database.CreateItemLink(augtest->ID),
						augtest->ID
					).c_str()
				);

				return false;
			}
			// check that augment is an actual augment
			else if(augtest->AugType == 0) {
				Message(
					Chat::Red,
					fmt::format(
						"{} ({}) in Augment Slot {} is not an actual augment.",
						database.CreateItemLink(augtest->ID),
						augtest->ID,
						augment_slot
					).c_str()
				);
				LogInventory(
					"Player [{}] on account [{}] attempted to use a non-augment item (Augment Slot [{}]) as an augment.\n"
					"Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n",
					GetName(),
					account_name,
					item->ID,
					augment_slot,
					aug1,
					aug2,
					aug3,
					aug4,
					aug5,
					aug6
				);

				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 && ((Admin() < augtest->MinStatus) || (Admin() < RuleI(GM, MinStatusToSummonItem)))) {
				Message(Chat::Red, "You are not a GM or do not have the status to summon this augment.");
				LogInventory("Player [{}] on account [{}] attempted to create a GM-only augment (Aug[{}]) with a status of [{}].\n(Item: [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], MinStatus: [{}])\n",
					GetName(), account_name, augment_slot, Admin(), item->ID, aug1, aug2, aug3, aug4, aug5, aug6, item->MinStatus);

				return false;
			}
			*/

			// check for augment type allowance
			if(enforce_wearable) {
				if ((item->AugSlotType[iter] == EQ::item::AugTypeNone) || !(((uint32)1 << (item->AugSlotType[iter] - 1)) & augtest->AugType)) {
					Message(
						Chat::Red,
						fmt::format(
							"Augment {} ({}) in Augment Slot {} is not capable of being socketed in to {} ({}).",
							database.CreateItemLink(augments[iter]),
							augments[iter],
							augment_slot,
							database.CreateItemLink(item->ID),
							item->ID
						).c_str()
					);
					LogInventory(
						"Player [{}] on account [{}] attempted to augment an item with an unacceptable augment type (Aug[{}]).\n"
						"Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n",
						GetName(),
						account_name,
						augment_slot,
						item->ID,
						aug1,
						aug2,
						aug3,
						aug4,
						aug5,
						aug6
					);

					return false;
				}

				if (!RuleB(Items, SummonItemAllowInvisibleAugments) && item->AugSlotVisible[iter] == 0) {
					Message(
						Chat::Red,
						fmt::format(
							"{} ({}) has not evolved enough to accept {} ({}) in Augment Slot {}.",
							database.CreateItemLink(item->ID),
							item->ID,
							database.CreateItemLink(augments[iter]),
							augments[iter],
							augment_slot
						).c_str()
					);
					LogInventory(
						"Player [{}] on account [{}] attempted to augment an unevolved item with augment type (Aug[{}]).\n"
						"Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n",
						GetName(),
						account_name,
						augment_slot,
						item->ID,
						aug1,
						aug2,
						aug3,
						aug4,
						aug5,
						aug6
					);

					return false;
				}
			}

			// check for augment to item restriction
			if (enforce_restrictions) {
				bool is_restricted = IsAugmentRestricted(item->ItemType, augtest->AugRestrict);

				if (is_restricted) {
					Message(
						Chat::Red,
						fmt::format(
							"{} ({}) in Augment Slot {} is restricted from being augmented in to {} ({}).",
							database.CreateItemLink(augments[iter]),
							augments[iter],
							augment_slot,
							database.CreateItemLink(item->ID),
							item->ID
						).c_str()
					);
					LogInventory(
						"Player [{}] on account [{}] attempted to augment an item with a restricted augment (Aug[{}]).\n"
						"Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n",
						GetName(),
						account_name,
						augment_slot,
						item->ID,
						aug1,
						aug2,
						aug3,
						aug4,
						aug5,
						aug6
					);

					return false;
				}
			}

			if(enforce_usable) {
				// check for class usability
				if(item->Classes && !(classes &= augtest->Classes)) {
					Message(
						Chat::Red,
						fmt::format(
							"{} ({}) in Augment Slot {} will result in an item unusable by any class.",
							database.CreateItemLink(augments[iter]),
							augments[iter],
							augment_slot
						).c_str()
					);
					LogInventory(
						"Player [{}] on account [{}] attempted to create an item unusable by any class.\n"
						"Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n",
						GetName(),
						account_name,
						item->ID,
						aug1,
						aug2,
						aug3,
						aug4,
						aug5,
						aug6
					);

					return false;
				}

				// check for race usability
				if(item->Races && !(races &= augtest->Races)) {
					Message(
						Chat::Red,
						fmt::format(
							"{} ({}) in Augment Slot {} will result in an item unusable by any race.",
							database.CreateItemLink(augments[iter]),
							augments[iter],
							augment_slot
						).c_str()
					);
					LogInventory(
						"Player [{}] on account [{}] attempted to create an item unusable by any race.\n"
						"Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n",
						GetName(),
						account_name,
						item->ID,
						aug1,
						aug2,
						aug3,
						aug4,
						aug5,
						aug6
					);

					return false;
				}

				// check for slot usability
				if(item->Slots && !(slots &= augtest->Slots)) {
					Message(
						Chat::Red,
						fmt::format(
							"{} ({}) in Augment Slot {} will result in an item unusable in any slot.",
							database.CreateItemLink(augments[iter]),
							augments[iter],
							augment_slot
						).c_str()
					);
					LogInventory(
						"Player [{}] on account [{}] attempted to create an item unusable in any slot.\n"
						"Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n",
						GetName(),
						account_name,
						item->ID,
						aug1,
						aug2,
						aug3,
						aug4,
						aug5,
						aug6
					);

					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;
	} else if(charges == -1) { // if the charges is -1, then no charge value was passed in set to max charges
		charges = item->MaxCharges;
	}

	// in any other situation just use charges as passed

	EQ::ItemInstance* inst = database.CreateItem(item, charges);
	auto timestamps = database.GetItemRecastTimestamps(CharacterID());
	if (inst) {
		const auto* d = inst->GetItem();
		if (d->RecastDelay) {
			if (d->RecastType != RECAST_TYPE_UNLINKED_ITEM) {
				inst->SetRecastTimestamp(timestamps.count(d->RecastType) ? timestamps.at(d->RecastType) : 0);
			}
			else {
				inst->SetRecastTimestamp(timestamps.count(d->ID) ? timestamps.at(d->ID) : 0);
			}
		}
	}

	if(inst == nullptr) {
		Message(Chat::Red, "An unknown server error has occurred and your item was not created.");
		// this goes to logfile since this is a major error
		LogError(
			"Player [{}] on account [{}] encountered an unknown item creation error.\n"
			"Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n",
			GetName(),
			account_name,
			item->ID,
			aug1,
			aug2,
			aug3,
			aug4,
			aug5,
			aug6
		);

		return false;
	}

	// add any validated augments
	for (int iter = EQ::invaug::SOCKET_BEGIN; iter <= EQ::invaug::SOCKET_END; ++iter) {
		if(augments[iter]) {
			inst->PutAugment(&database, iter, augments[iter]);
		}
	}

	// attune item
	if(attuned && inst->GetItem()->Attuneable) {
		inst->SetAttuned(true);
	}

	inst->SetOrnamentIcon(ornament_icon);
	inst->SetOrnamentationIDFile(ornament_idfile);
	inst->SetOrnamentHeroModel(ornament_hero_model);

	// check to see if item is usable in requested slot
	if (enforce_usable && (to_slot >= EQ::invslot::EQUIPMENT_BEGIN && to_slot <= EQ::invslot::EQUIPMENT_END)) {
		uint32 slottest = to_slot;
		if(!(slots & ((uint32)1 << slottest))) {
			Message(
				Chat::White,
				fmt::format(
					"{} ({}) cannot be equipped in {} ({}), moving to cursor.",
					database.CreateItemLink(item->ID),
					item->ID,
					EQ::invslot::GetInvPossessionsSlotName(to_slot),
					to_slot
				).c_str()
			);
			LogInventory(
				"Player [{}] on account [{}] attempted to equip an item unusable in slot [{}] - moved to cursor.\n"
				"Item [{}], Aug1: [{}], Aug2: [{}], Aug3: [{}], Aug4: [{}], Aug5: [{}], Aug6: [{}])\n",
				GetName(),
				account_name,
				to_slot,
				item->ID,
				aug1,
				aug2,
				aug3,
				aug4,
				aug5,
				aug6
			);
			to_slot = EQ::invslot::slotCursor;
		}
	}

	if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::ITEM_CREATION)) {
		auto e = PlayerEvent::ItemCreationEvent{};
		e.item_id      = item->ID;
		e.item_name    = item->Name;
		e.to_slot      = to_slot;
		e.charges      = charges;
		e.augment_1_id = aug1;
		e.augment_2_id = aug2;
		e.augment_3_id = aug3;
		e.augment_4_id = aug4;
		e.augment_5_id = aug5;
		e.augment_6_id = aug6;
		e.attuned      = attuned;

		RecordPlayerEventLog(PlayerEvent::ITEM_CREATION, e);
	}

	// put item into inventory
	if (to_slot == EQ::invslot::slotCursor) {
		PushItemOnCursor(*inst);
		SendItemPacket(EQ::invslot::slotCursor, inst, ItemPacketLimbo);
	} else {
		PutItemInInventory(to_slot, *inst, true);
	}

	m_external_handin_items_returned.emplace_back(inst->GetItem()->ID);

	safe_delete(inst);

	// discover item and any augments
	if (
		RuleB(Character, EnableDiscoveredItems) &&
		!GetGM() &&
		!IsDiscovered(item_id)
	) {
		DiscoverItem(item_id);
	}

	return true;
}

// Drop item from inventory to ground (generally only dropped from SLOT_CURSOR)
void Client::DropItem(int16 slot_id, bool recurse)
{
	LogInventory(
		"[{}] (char_id: [{}]) Attempting to drop item from slot [{}] on the ground",
		GetCleanName(),
		CharacterID(),
		slot_id
	);

	if (GetInv().CheckNoDrop(slot_id, recurse) && !CanTradeFVNoDropItem()) {
		auto invalid_drop = m_inv.GetItem(slot_id);
		if (!invalid_drop) {
			LogInventory("Error in InventoryProfile::CheckNoDrop() - returned 'true' for empty slot");
		}
		else {
			if (EQEmuLogSys::Instance()->log_settings[Logs::Inventory].is_category_enabled) {
				LogInventory("DropItem() Hack detected - full item parse:");
				LogInventory("depth: 0, Item: [{}] (id: [{}]), IsDroppable: [{}]",
					(invalid_drop->GetItem() ? invalid_drop->GetItem()->Name : "null data"), invalid_drop->GetID(), (invalid_drop->IsDroppable(false) ? "true" : "false"));

				for (auto iter1 : *invalid_drop->GetContents()) { // depth 1
					LogInventory("-depth: 1, Item: [{}] (id: [{}]), IsDroppable: [{}]",
						(iter1.second->GetItem() ? iter1.second->GetItem()->Name : "null data"), iter1.second->GetID(), (iter1.second->IsDroppable(false) ? "true" : "false"));

					for (auto iter2 : *iter1.second->GetContents()) { // depth 2
						LogInventory("--depth: 2, Item: [{}] (id: [{}]), IsDroppable: [{}]",
							(iter2.second->GetItem() ? iter2.second->GetItem()->Name : "null data"), iter2.second->GetID(), (iter2.second->IsDroppable(false) ? "true" : "false"));
					}
				}
			}
		}

		std::string message = fmt::format(
			"Tried to drop an item on the ground that was no-drop! item_name [{}] item_id ({})",
			invalid_drop->GetItem()->Name,
			invalid_drop->GetItem()->ID
		);

		invalid_drop = nullptr;
		RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
		GetInv().DeleteItem(slot_id);
		return;
	}

	// Take control of item in client inventory
	auto* inst = m_inv.PopItem(slot_id);
	if (inst) {
		if (EQEmuLogSys::Instance()->log_settings[Logs::Inventory].is_category_enabled) {
			LogInventory("DropItem() Processing - full item parse:");
			LogInventory(
				"depth: 0, Item: [{}] (id: [{}]), IsDroppable: [{}]",
				(inst->GetItem() ? inst->GetItem()->Name : "null data"),
				inst->GetID(),
				(inst->IsDroppable(false) ? "true" : "false")
			);

			if (!inst->IsDroppable(false)) {
				LogError("Non-droppable item being processed for drop by [{}]", GetCleanName());
			}

			for (auto iter1 : *inst->GetContents()) { // depth 1
				LogInventory(
					"-depth: 1, Item: [{}] (id: [{}]), IsDroppable: [{}]",
					(iter1.second->GetItem() ? iter1.second->GetItem()->Name : "null data"),
					iter1.second->GetID(),
					(iter1.second->IsDroppable(false) ? "true" : "false")
				);

				if (!iter1.second->IsDroppable(false)) {
					LogError("Non-droppable item being processed for drop by [{}]", GetCleanName());
				}

				for (auto iter2 : *iter1.second->GetContents()) { // depth 2
					LogInventory(
						"--depth: 2, Item: [{}] (id: [{}]), IsDroppable: [{}]",
						(iter2.second->GetItem() ? iter2.second->GetItem()->Name : "null data"),
						iter2.second->GetID(),
						(iter2.second->IsDroppable(false) ? "true" : "false")
					);

					if (!iter2.second->IsDroppable(false)) {
						LogError("Non-droppable item being processed for drop by [{}]", GetCleanName());
					}
				}
			}
		}


		int i = 0;

		if (inst && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::DROPPED_ITEM)) {
			auto e = PlayerEvent::DroppedItemEvent{
				.item_id      = inst->GetID(),
				.augment_1_id = inst->GetAugmentItemID(0),
				.augment_2_id = inst->GetAugmentItemID(1),
				.augment_3_id = inst->GetAugmentItemID(2),
				.augment_4_id = inst->GetAugmentItemID(3),
				.augment_5_id = inst->GetAugmentItemID(4),
				.augment_6_id = inst->GetAugmentItemID(5),
				.item_name    = inst->GetItem()->Name,
				.slot_id      = slot_id,
				.charges      = (uint32) inst->GetCharges()
			};
			RecordPlayerEventLog(PlayerEvent::DROPPED_ITEM, e);
		}

		if (parse->ItemHasQuestSub(inst, EVENT_DROP_ITEM)) {
			parse->EventItem(EVENT_DROP_ITEM, this, inst, nullptr, "", slot_id);
			if (i != 0) {
				LogInventory("Item drop handled by [EVENT_DROP_ITEM]");
				safe_delete(inst);
			}
		}

		if (parse->PlayerHasQuestSub(EVENT_DROP_ITEM_CLIENT)) {
			std::vector<std::any> args = { inst };

			i = parse->EventPlayer(EVENT_DROP_ITEM_CLIENT, this, "", slot_id, &args);
			if (i != 0) {
				LogInventory("Item drop handled by [EVENT_DROP_ITEM_CLIENT]");
				safe_delete(inst);
			}
		}
	} else {
		// Item doesn't exist in inventory!
		LogInventory("DropItem() - No item found in slot [{}]", slot_id);
		Message(
			Chat::Red,
			fmt::format(
				"Error: Item not found in slot {}.",
				slot_id
			).c_str()
		);
		return;
	}

	// Save client inventory change to database
	if (slot_id == EQ::invslot::slotCursor) {
		SendCursorBuffer();
		auto s = m_inv.cursor_cbegin(), e = m_inv.cursor_cend();
		database.SaveCursor(CharacterID(), s, e);
	} else {
		database.SaveInventory(CharacterID(), nullptr, slot_id);
	}

	if (!inst) {
		return;
	}

	// Package as zone object
	auto object = new Object(this, inst);
	entity_list.AddObject(object, true);
	object->StartDecay();

	LogInventory("[{}] dropped [{}] from slot [{}]", GetCleanName(), inst->GetItem()->Name, slot_id);

	safe_delete(inst);
}

// Drop inst
void Client::DropInst(const EQ::ItemInstance* inst)
{
	if (!inst) {
		// Item doesn't exist in inventory!
		Message(Chat::Red, "Error: Item not found");
		return;
	}


	if (inst->GetItem()->NoDrop == 0)
	{
		Message(Chat::Red, "This item is NODROP. Deleting.");
		return;
	}

	// Package as zone object
	auto 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) {
	if (slot_id <= EQ::invslot::POSSESSIONS_END && slot_id >= EQ::invslot::POSSESSIONS_BEGIN) {
		if ((((uint64)1 << slot_id) & GetInv().GetLookup()->PossessionsBitmask) == 0)
			return INVALID_ID;
	}
	else if (slot_id <= EQ::invbag::GENERAL_BAGS_END && slot_id >= EQ::invbag::GENERAL_BAGS_BEGIN) {
		auto temp_slot = EQ::invslot::GENERAL_BEGIN + ((slot_id - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT);
		if ((((uint64)1 << temp_slot) & GetInv().GetLookup()->PossessionsBitmask) == 0)
			return INVALID_ID;
	}
	else if (slot_id <= EQ::invslot::BANK_END && slot_id >= EQ::invslot::BANK_BEGIN) {
		if ((slot_id - EQ::invslot::BANK_BEGIN) >= GetInv().GetLookup()->InventoryTypeSize.Bank)
			return INVALID_ID;
	}
	else if (slot_id <= EQ::invbag::BANK_BAGS_END && slot_id >= EQ::invbag::BANK_BAGS_BEGIN) {
		auto temp_slot = (slot_id - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT;
		if (temp_slot >= GetInv().GetLookup()->InventoryTypeSize.Bank)
			return INVALID_ID;
	}

	const EQ::ItemInstance* 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-5)
int32 Client::GetAugmentIDAt(int16 slot_id, uint8 augslot) {
	if (slot_id <= EQ::invslot::POSSESSIONS_END && slot_id >= EQ::invslot::POSSESSIONS_BEGIN) {
		if ((((uint64)1 << slot_id) & GetInv().GetLookup()->PossessionsBitmask) == 0)
			return INVALID_ID;
	}
	else if (slot_id <= EQ::invbag::GENERAL_BAGS_END && slot_id >= EQ::invbag::GENERAL_BAGS_BEGIN) {
		auto temp_slot = EQ::invslot::GENERAL_BEGIN + ((slot_id - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT);
		if ((((uint64)1 << temp_slot) & GetInv().GetLookup()->PossessionsBitmask) == 0)
			return INVALID_ID;
	}
	else if (slot_id <= EQ::invslot::BANK_END && slot_id >= EQ::invslot::BANK_BEGIN) {
		if ((slot_id - EQ::invslot::BANK_BEGIN) >= GetInv().GetLookup()->InventoryTypeSize.Bank)
			return INVALID_ID;
	}
	else if (slot_id <= EQ::invbag::BANK_BAGS_END && slot_id >= EQ::invbag::BANK_BAGS_BEGIN) {
		auto temp_slot = (slot_id - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT;
		if (temp_slot >= GetInv().GetLookup()->InventoryTypeSize.Bank)
			return INVALID_ID;
	}

	const EQ::ItemInstance* inst = m_inv[slot_id];
	if (inst && inst->GetAugmentItemID(augslot)) {
		return inst->GetAugmentItemID(augslot);
	}

	// None found
	return INVALID_ID;
}

void Client::SendCursorBuffer()
{
	// Temporary work-around for the RoF+ Client Buffer
	// Instead of dealing with client moving items in cursor buffer,
	// we can just send the next item in the cursor buffer to the cursor.
	if (ClientVersion() < EQ::versions::ClientVersion::RoF) { return; }
	if (GetInv().CursorEmpty()) { return; }

	auto test_inst = GetInv().GetCursorItem();
	if (test_inst == nullptr) { return; }
	auto test_item = test_inst->GetItem();
	if (test_item == nullptr) { return; }

	bool lore_pass = true;
	if (test_item->LoreGroup == -1) {
		lore_pass = (m_inv.HasItem(test_item->ID, 0, ~(invWhereSharedBank | invWhereCursor)) == INVALID_INDEX);
	}
	else if (test_item->LoreGroup != 0) {
		lore_pass = (m_inv.HasItemByLoreGroup(test_item->LoreGroup, ~(invWhereSharedBank | invWhereCursor)) == INVALID_INDEX);
	}

	if (!lore_pass) {
		LogInventory("([{}]) Duplicate lore items are not allowed - destroying item [{}](id:[{}]) on cursor",
			GetName(), test_item->Name, test_item->ID);
		MessageString(Chat::Loot, 290);

		if (parse->ItemHasQuestSub(test_inst, EVENT_DESTROY_ITEM)) {
			parse->EventItem(EVENT_DESTROY_ITEM, this, test_inst, nullptr, "", 0);
		}

		if (parse->PlayerHasQuestSub(EVENT_DESTROY_ITEM_CLIENT)) {
			std::vector<std::any> args = { test_inst };
			parse->EventPlayer(EVENT_DESTROY_ITEM_CLIENT, this, "", 0, &args);
		}

		DeleteItemInInventory(EQ::invslot::slotCursor);
		SendCursorBuffer();
	}
	else {
		SendItemPacket(EQ::invslot::slotCursor, test_inst, ItemPacketLimbo);
	}
}

// Remove item from inventory
void Client::DeleteItemInInventory(int16 slot_id, int16 quantity, bool client_update, bool update_db) {
	#if (EQDEBUG >= 5)
		LogDebug("DeleteItemInInventory([{}], [{}], [{}])", 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;
	}

	uint64 evolve_id = m_inv[slot_id]->GetEvolveUniqueID();
	bool   isDeleted = m_inv.DeleteItem(slot_id, quantity);
	if (isDeleted && evolve_id && (slot_id > EQ::invslot::TRADE_END || slot_id < EQ::invslot::TRADE_BEGIN)) {
		CharacterEvolvingItemsRepository::SoftDelete(database, evolve_id);
	}

	const EQ::ItemInstance* inst = nullptr;
	if (slot_id == EQ::invslot::slotCursor) {
		auto s = m_inv.cursor_cbegin(), e = m_inv.cursor_cend();
		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 = nullptr;
		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);
		}
	}
}

bool Client::PushItemOnCursor(const EQ::ItemInstance& inst, bool client_update)
{
	LogInventory("Putting item [{}] ([{}]) on the cursor", inst.GetItem()->Name, inst.GetItem()->ID);

	EvolvingItemsManager::Instance()->DoLootChecks(CharacterID(), EQ::invslot::slotCursor, inst);
	m_inv.PushCursor(inst);

	if (client_update) {
		SendItemPacket(EQ::invslot::slotCursor, &inst, ItemPacketLimbo);
	}

	auto s = m_inv.cursor_cbegin(), e = m_inv.cursor_cend();
	return database.SaveCursor(CharacterID(), s, e);
}

// 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::PutItemInInventory(int16 slot_id, const EQ::ItemInstance& inst, bool client_update) {
	LogInventory("Putting item [{}] ([{}]) into slot [{}]", inst.GetItem()->Name, inst.GetItem()->ID, slot_id);

	if (slot_id == EQ::invslot::slotCursor) { // don't trust macros before conditional statements...
		return PushItemOnCursor(inst, client_update);
	}

	EvolvingItemsManager::Instance()->DoLootChecks(CharacterID(), slot_id, inst);
	m_inv.PutItem(slot_id, inst);

	if (client_update)
	{
		SendItemPacket(slot_id, &inst, ((slot_id == EQ::invslot::slotCursor) ? ItemPacketLimbo : ItemPacketTrade));
		//SendWearChange(EQ::InventoryProfile::CalcMaterialFromSlot(slot_id));
	}

	CalcBonuses();

	if (slot_id == EQ::invslot::slotCursor) {
		auto s = m_inv.cursor_cbegin(), e = m_inv.cursor_cend();
		return database.SaveCursor(CharacterID(), s, e);
	}

	return database.SaveInventory(CharacterID(), &inst, slot_id);

	//CalcBonuses(); // this never fires??
	// a lot of wasted checks and calls coded above...
}

void Client::PutLootInInventory(int16 slot_id, const EQ::ItemInstance &inst, LootItem** bag_item_data)
{
	LogInventory("Putting loot item [{}] ([{}]) into slot [{}]", inst.GetItem()->Name, inst.GetItem()->ID, slot_id);

	bool cursor_empty = m_inv.CursorEmpty();

	EvolvingItemsManager::Instance()->DoLootChecks(CharacterID(), slot_id, inst);

	if (slot_id == EQ::invslot::slotCursor) {
		m_inv.PushCursor(inst);
		auto s = m_inv.cursor_cbegin(), e = m_inv.cursor_cend();
		database.SaveCursor(CharacterID(), s, e);
	}
	else {
		m_inv.PutItem(slot_id, inst);
		database.SaveInventory(CharacterID(), &inst, slot_id);
	}

	// Subordinate items in cursor buffer must be sent via ItemPacketSummonItem or we just overwrite the visible cursor and desync the client
	if (slot_id == EQ::invslot::slotCursor && !cursor_empty) {
		// RoF+ currently has a specialized cursor handler
		if (ClientVersion() < EQ::versions::ClientVersion::RoF)
			SendItemPacket(slot_id, &inst, ItemPacketLimbo);
	}
	else {
		SendLootItemInPacket(&inst, slot_id);
	}

	if (bag_item_data) {
		for (int index = EQ::invbag::SLOT_BEGIN; index <= EQ::invbag::SLOT_END; ++index) {
			if (bag_item_data[index] == nullptr)
				continue;

			const EQ::ItemInstance *bagitem = database.CreateItem(
				bag_item_data[index]->item_id,
				bag_item_data[index]->charges,
				bag_item_data[index]->aug_1,
				bag_item_data[index]->aug_2,
				bag_item_data[index]->aug_3,
				bag_item_data[index]->aug_4,
				bag_item_data[index]->aug_5,
				bag_item_data[index]->aug_6,
				bag_item_data[index]->attuned,
				bag_item_data[index]->custom_data,
				bag_item_data[index]->ornamenticon,
				bag_item_data[index]->ornamentidfile,
				bag_item_data[index]->ornament_hero_model
				);

			// Dump bag contents to cursor in the event that owning bag is not the first cursor item
			// (This assumes that the data passed is correctly associated..no safety checks are implemented)
			if (slot_id == EQ::invslot::slotCursor && !cursor_empty) {
				LogInventory("Putting bag loot item [{}] ([{}]) into slot [{}] (non-empty cursor override)",
					inst.GetItem()->Name, inst.GetItem()->ID, EQ::invslot::slotCursor);

				PutLootInInventory(EQ::invslot::slotCursor, *bagitem);
			}
			else {
				auto bag_slot = EQ::InventoryProfile::CalcSlotId(slot_id, index);

				LogInventory("Putting bag loot item [{}] ([{}]) into slot [{}] (bag slot [{}])",
					inst.GetItem()->Name, inst.GetItem()->ID, bag_slot, index);

				PutLootInInventory(bag_slot, *bagitem);
			}
			safe_delete(bagitem);
		}
	}

	CalcBonuses();
}
bool Client::TryStacking(EQ::ItemInstance* 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 = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++) {
		if ((((uint64)1 << i) & GetInv().GetLookup()->PossessionsBitmask) == 0)
			continue;

		EQ::ItemInstance* 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 = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++) {
		if ((((uint64)1 << i) & GetInv().GetLookup()->PossessionsBitmask) == 0)
			continue;

		for (uint8 j = EQ::invbag::SLOT_BEGIN; j <= EQ::invbag::SLOT_END; j++) {
			uint16 slotid = EQ::InventoryProfile::CalcSlotId(i, j);
			EQ::ItemInstance* 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(EQ::ItemInstance& inst, bool try_worn, bool try_cursor, LootItem** bag_item_data)
{
	// #1: Try to auto equip
	if (try_worn && inst.IsEquipable(GetBaseRace(), GetClass()) && inst.GetItem()->ReqLevel <= level && (!inst.GetItem()->Attuneable || inst.IsAttuned()) && inst.GetItem()->ItemType != EQ::item::ItemTypeAugmentation) {
		for (int16 i = EQ::invslot::EQUIPMENT_BEGIN; i <= EQ::invslot::EQUIPMENT_END; i++) {
			if ((((uint64)1 << i) & GetInv().GetLookup()->PossessionsBitmask) == 0)
				continue;

			if (!m_inv[i]) {
				if (i == EQ::invslot::slotPrimary && inst.IsWeapon()) { // If item is primary slot weapon
					if (inst.GetItem()->IsType2HWeapon()) { // and uses 2hs \ 2hb \ 2hp
						if (m_inv[EQ::invslot::slotSecondary]) { // and if secondary slot is not empty
							continue; // Can't auto-equip
						}
					}
				}
				if( i == EQ::invslot::slotPrimary && m_inv[EQ::invslot::slotSecondary] ) {
					uint8 instrument = m_inv[EQ::invslot::slotSecondary]->GetItem()->ItemType;
					if(
							instrument == EQ::item::ItemTypeWindInstrument ||
							instrument == EQ::item::ItemTypeStringedInstrument ||
							instrument == EQ::item::ItemTypeBrassInstrument ||
							instrument == EQ::item::ItemTypePercussionInstrument
							) {
						LogInventory("Cannot equip a primary item with [{}] already in the secondary.", m_inv[EQ::invslot::slotSecondary]->GetItem()->Name);
						continue; // Do not auto-equip Primary when instrument is in Secondary
					}
				}
				if (i == EQ::invslot::slotSecondary && m_inv[EQ::invslot::slotPrimary]) { // check to see if primary slot is a two hander
					uint8 instrument = inst.GetItem()->ItemType;
					if(
							instrument == EQ::item::ItemTypeWindInstrument ||
							instrument == EQ::item::ItemTypeStringedInstrument ||
							instrument == EQ::item::ItemTypeBrassInstrument ||
							instrument == EQ::item::ItemTypePercussionInstrument
							) {
						LogInventory("Cannot equip a secondary instrument with [{}] already in the primary.", m_inv[EQ::invslot::slotPrimary]->GetItem()->Name);
						continue; // Do not auto-equip instrument in Secondary when Primary is equipped.
					}

					uint8 use = m_inv[EQ::invslot::slotPrimary]->GetItem()->ItemType;
					if(use == EQ::item::ItemType2HSlash || use == EQ::item::ItemType2HBlunt || use == EQ::item::ItemType2HPiercing) {
						continue;
					}
				}
				if (i == EQ::invslot::slotSecondary && inst.IsWeapon() && !CanThisClassDualWield()) {
					continue;
				}

				if (inst.IsEquipable(i)) { // Equippable at this slot?
					//send worn to everyone...
					PutLootInInventory(i, inst);
					uint8 worn_slot_material = EQ::InventoryProfile::CalcMaterialFromSlot(i);
					if (worn_slot_material != EQ::textures::materialInvalid) {
						SendWearChange(worn_slot_material);
					}

					if (parse->ItemHasQuestSub(&inst, EVENT_EQUIP_ITEM)) {
						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 == EQ::item::ItemTypeArrow) ? true : false;
	int16 slot_id = m_inv.FindFreeSlot(inst.IsClassBag(), try_cursor, inst.GetItem()->Size, is_arrow);
	if (slot_id != INVALID_INDEX) {
		PutLootInInventory(slot_id, inst, bag_item_data);
		return true;
	}

	return false;
}

// helper function for AutoPutLootInInventory
void Client::MoveItemCharges(EQ::ItemInstance &from, int16 to_slot, uint8 type)
{
	EQ::ItemInstance *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 == EQ::invslot::slotCursor) {
			auto s = m_inv.cursor_cbegin(), e = m_inv.cursor_cend();
			database.SaveCursor(CharacterID(), s, e);
		}
		else {
			database.SaveInventory(CharacterID(), tmp_inst, to_slot);
		}
	}
}

#if 0
// TODO: needs clean-up to save references
bool MakeItemLink(char* &ret_link, const ItemData *item, uint32 aug0, uint32 aug1, uint32 aug2, uint32 aug3, uint32 aug4, uint32 aug5, uint8 evolving, uint8 evolvedlevel) {
	//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 (!item) //have to have an item to make the link
		return false;

	//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

	//int hash = GetItemLinkHash(inst);	//eventually this will work (currently crashes zone), but for now we'll skip the extra overhead
	int hash = NOT_USED;

	// Tested with UF and RoF..there appears to be a problem with using non-augment arguments below...
	// Currently, enabling them causes misalignments in what the client expects. I haven't looked
	// into it further to determine the cause..but, the function is setup to accept the parameters.
	// Note: some links appear with '00000' in front of the name..so, it's likely we need to send
	// some additional information when certain parameters are true
	//switch (GetClientVersion()) {
	switch (0) {
	case EQClientRoF2:
		// This operator contains 14 parameter masks..but, only 13 parameter values.
		// Even so, the client link appears ok... Need to figure out the discrepancy
		MakeAnyLenString(&ret_link, "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%1X" "%04X" "%1X" "%05X" "%08X",
			0,
			item->ID,
			aug0,
			aug1,
			aug2,
			aug3,
			aug4,
			aug5,
			0,//evolving,
			0,//item->LoreGroup,
			0,//evolvedlevel,
			0,
			hash
			);
		return true;
	case EQClientRoF:
		MakeAnyLenString(&ret_link, "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%1X" "%05X" "%08X",
			0,
			item->ID,
			aug0,
			aug1,
			aug2,
			aug3,
			aug4,
			aug5,
			0,//evolving,
			0,//item->LoreGroup,
			0,//evolvedlevel,
			0,
			hash
			);
		return true;
	case EQClientUnderfoot:
	case EQClientSoD:
	case EQClientSoF:
		MakeAnyLenString(&ret_link, "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%1X" "%05X" "%08X",
			0,
			item->ID,
			aug0,
			aug1,
			aug2,
			aug3,
			aug4,
			0,//evolving,
			0,//item->LoreGroup,
			0,//evolvedlevel,
			0,
			hash
			);
		return true;
	case EQClientTitanium:
		MakeAnyLenString(&ret_link, "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%1X" "%08X",
			0,
			item->ID,
			aug0,
			aug1,
			aug2,
			aug3,
			aug4,
			0,//evolving,
			0,//item->LoreGroup,
			0,//evolvedlevel,
			hash
			);
		return true;
	case EQClient62:
	default:
		return false;
	}
}
#endif

int Client::GetItemLinkHash(const EQ::ItemInstance* inst) {
#if 0
	//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 EQ::ItemData* 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;
#endif
	return 0;
}

// This appears to still be in use... The core of this should be incorporated into class EQ::SayLinkEngine
void Client::SendItemLink(const EQ::ItemInstance* 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 EQ::ItemData* item = inst->GetItem();
	const char* name2 = &item->Name[0];
	auto 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 = 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(CastToMob(),outapp,true,200,0,false);
	safe_delete(outapp);
}

void Client::SendLootItemInPacket(const EQ::ItemInstance* inst, int16 slot_id)
{
	SendItemPacket(slot_id,inst, ItemPacketTrade);
}

bool Client::IsValidSlot(uint32 slot) {
	if (slot <= EQ::invslot::POSSESSIONS_END && slot >= EQ::invslot::POSSESSIONS_BEGIN) {
		return ((((uint64)1 << slot) & GetInv().GetLookup()->PossessionsBitmask) != 0);
	}
	else if (slot <= EQ::invbag::GENERAL_BAGS_END && slot >= EQ::invbag::GENERAL_BAGS_BEGIN) {
		auto temp_slot = EQ::invslot::GENERAL_BEGIN + ((slot - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT);
		return ((((uint64)1 << temp_slot) & GetInv().GetLookup()->PossessionsBitmask) != 0);
	}
	else if (slot <= EQ::invslot::BANK_END && slot >= EQ::invslot::BANK_BEGIN) {
		return ((slot - EQ::invslot::BANK_BEGIN) < GetInv().GetLookup()->InventoryTypeSize.Bank);
	}
	else if (slot <= EQ::invbag::BANK_BAGS_END && slot >= EQ::invbag::BANK_BAGS_BEGIN) {
		auto temp_slot = (slot - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT;
		return (temp_slot < GetInv().GetLookup()->InventoryTypeSize.Bank);
	}
	else if (
		(slot == (uint32)INVALID_INDEX) ||
		(slot == (uint32)EQ::invslot::slotCursor) ||
		(slot <= EQ::invbag::CURSOR_BAG_END && slot >= EQ::invbag::CURSOR_BAG_BEGIN) ||
		(slot <= EQ::invslot::TRIBUTE_END && slot >= EQ::invslot::TRIBUTE_BEGIN) ||
		(slot <= EQ::invslot::GUILD_TRIBUTE_END && slot >= EQ::invslot::GUILD_TRIBUTE_BEGIN) ||
		(slot <= EQ::invslot::SHARED_BANK_END && slot >= EQ::invslot::SHARED_BANK_BEGIN) ||
		(slot <= EQ::invbag::SHARED_BANK_BAGS_END && slot >= EQ::invbag::SHARED_BANK_BAGS_BEGIN) ||
		(slot <= EQ::invslot::TRADE_END && slot >= EQ::invslot::TRADE_BEGIN) ||
		(slot <= EQ::invslot::WORLD_END && slot >= EQ::invslot::WORLD_BEGIN)
	) {
		return true;
	}

	return false;
}

bool Client::IsBankSlot(uint32 slot)
{
	if ((slot >= EQ::invslot::BANK_BEGIN && slot <= EQ::invslot::BANK_END) ||
		(slot >= EQ::invbag::BANK_BAGS_BEGIN && slot <= EQ::invbag::BANK_BAGS_END) ||
		(slot >= EQ::invslot::SHARED_BANK_BEGIN && slot <= EQ::invslot::SHARED_BANK_END) ||
		(slot >= EQ::invbag::SHARED_BANK_BAGS_BEGIN && slot <= EQ::invbag::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(Chat::Red, "Warning: Invalid slot move from slot %u to slot %u with %u charges!", src_slot_check, dst_slot_check, stack_count_check);
		LogInventory("Invalid slot move from slot [{}] to slot [{}] with [{}] 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(Chat::Red, "Warning: Invalid slot move from slot %u to slot %u with %u charges!", src_slot_check, dst_slot_check, stack_count_check);
		LogInventory("Invalid slot move from slot [{}] to slot [{}] with [{}] charges!", src_slot_check, dst_slot_check, stack_count_check);
		return false;
	}

	if (move_in->from_slot == move_in->to_slot) { // Item summon, no further processing needed
		if (ClientVersion() >= EQ::versions::ClientVersion::RoF) { return true; } // Can't do RoF+

		if (move_in->to_slot == EQ::invslot::slotCursor) {
			auto test_inst = m_inv.GetItem(EQ::invslot::slotCursor);
			if (test_inst == nullptr) { return true; }
			auto test_item = test_inst->GetItem();
			if (test_item == nullptr) { return true; }
			if (!test_item->LoreFlag) { return true; }

			bool lore_pass = true;
			if (test_item->LoreGroup == -1) {
				lore_pass = (m_inv.HasItem(test_item->ID, 0, ~(invWhereSharedBank | invWhereCursor)) == INVALID_INDEX);
			}
			else if (test_item->LoreGroup != 0) {
				lore_pass = (m_inv.HasItemByLoreGroup(test_item->LoreGroup, ~(invWhereSharedBank | invWhereCursor)) == INVALID_INDEX);
			}

			if (!lore_pass) {
				LogInventory("([{}]) Duplicate lore items are not allowed - destroying item [{}](id:[{}]) on cursor",
					GetName(), test_item->Name, test_item->ID);
				MessageString(Chat::Loot, 290);

				if (parse->ItemHasQuestSub(test_inst, EVENT_DESTROY_ITEM)) {
					parse->EventItem(EVENT_DESTROY_ITEM, this, test_inst, nullptr, "", 0);
				}

				if (parse->PlayerHasQuestSub(EVENT_DESTROY_ITEM_CLIENT)) {
					std::vector<std::any> args = { test_inst };
					parse->EventPlayer(EVENT_DESTROY_ITEM_CLIENT, this, "", 0, &args);
				}

				DeleteItemInInventory(EQ::invslot::slotCursor, 0, true);

				if (test_inst && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::ITEM_DESTROY)) {
					auto e = PlayerEvent::DestroyItemEvent{
						.item_id      = test_inst->GetItem()->ID,
						.item_name    = test_inst->GetItem()->Name,
						.charges      = test_inst->GetCharges(),
						.augment_1_id = test_inst->GetAugmentItemID(0),
						.augment_2_id = test_inst->GetAugmentItemID(1),
						.augment_3_id = test_inst->GetAugmentItemID(2),
						.augment_4_id = test_inst->GetAugmentItemID(3),
						.augment_5_id = test_inst->GetAugmentItemID(4),
						.augment_6_id = test_inst->GetAugmentItemID(5),
						.attuned      = test_inst->IsAttuned(),
						.reason       = "Duplicate lore item"
					};

					RecordPlayerEventLog(PlayerEvent::ITEM_DESTROY, e);
				}

			}
		}
		return true;
	}

	if (move_in->to_slot == (uint32)INVALID_INDEX) {
		if (move_in->from_slot == (uint32)EQ::invslot::slotCursor) {
			LogInventory("Client destroyed item from cursor slot [{}]", move_in->from_slot);

			EQ::ItemInstance *inst = m_inv.GetItem(EQ::invslot::slotCursor);

			if (inst) {
				if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::ITEM_DESTROY)) {
					auto e = PlayerEvent::DestroyItemEvent{
						.item_id      = inst->GetItem()->ID,
						.item_name    = inst->GetItem()->Name,
						.charges      = inst->GetCharges(),
						.augment_1_id = inst->GetAugmentItemID(0),
						.augment_2_id = inst->GetAugmentItemID(1),
						.augment_3_id = inst->GetAugmentItemID(2),
						.augment_4_id = inst->GetAugmentItemID(3),
						.augment_5_id = inst->GetAugmentItemID(4),
						.augment_6_id = inst->GetAugmentItemID(5),
						.attuned      = inst->IsAttuned(),
						.reason       = "Client destroy cursor"
					};

					RecordPlayerEventLog(PlayerEvent::ITEM_DESTROY, e);
				}

				if (parse->ItemHasQuestSub(inst, EVENT_DESTROY_ITEM)) {
					parse->EventItem(EVENT_DESTROY_ITEM, this, inst, nullptr, "", 0);
				}

				if (parse->PlayerHasQuestSub(EVENT_DESTROY_ITEM_CLIENT)) {
					std::vector<std::any> args = { inst };
					parse->EventPlayer(EVENT_DESTROY_ITEM_CLIENT, this, "", 0, &args);
				}
			}

			DeleteItemInInventory(move_in->from_slot);
			SendCursorBuffer();

			return true; // Item destroyed by client
		}
		else {
			LogInventory("Deleted item from slot [{}] as a result of an inventory container tradeskill combine", move_in->from_slot);
			DeleteItemInInventory(move_in->from_slot);
			return true; // Item deletion
		}
	}

	if (auto_attack) {
		if (move_in->from_slot == EQ::invslot::slotPrimary || move_in->from_slot == EQ::invslot::slotSecondary || move_in->from_slot == EQ::invslot::slotRange)
			SetAttackTimer();
		else if (move_in->to_slot == EQ::invslot::slotPrimary || move_in->to_slot == EQ::invslot::slotSecondary || move_in->to_slot == EQ::invslot::slotRange)
			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)
		{
			auto message = fmt::format(
				"Player tried to make use of a banker (items) but banker [{}] is "
				"non-existent or too far away [{}] units",
				banker ? banker->GetName() : "UNKNOWN NPC", distance
			);
			RecordPlayerEventLog(PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});

			Kick("Inventory desync");	// 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;
	EQ::ItemInstance* src_inst = m_inv.GetItem(src_slot_id);
	EQ::ItemInstance* dst_inst = m_inv.GetItem(dst_slot_id);
	if (src_inst){
		LogInventory("Src slot [{}] has item [{}] ([{}]) with [{}] 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(Chat::Red,"Error: Insufficient number in stack.");
			return false;
		}
	}
	if (dst_inst) {
		LogInventory("Dest slot [{}] has item [{}] ([{}]) with [{}] charges in it", dst_slot_id, dst_inst->GetItem()->Name, dst_inst->GetItem()->ID, dst_inst->GetCharges());
		dstitemid = dst_inst->GetItem()->ID;
	}
	if (IsBuyer() && srcitemid > 0) {
		CheckIfMovedItemIsPartOfBuyLines(srcitemid);
	}

	if (IsTrader() && srcitemid>0){
		EQ::ItemInstance* srcbag;
		EQ::ItemInstance* dstbag;
		uint32 srcbagid =0;
		uint32 dstbagid = 0;

		if (src_slot_id >= EQ::invbag::GENERAL_BAGS_BEGIN && src_slot_id <= EQ::invbag::GENERAL_BAGS_END) {
			srcbag = m_inv.GetItem(EQ::InventoryProfile::CalcSlotId(src_slot_id));
			if (srcbag)
				srcbagid = srcbag->GetItem()->ID;
		}
		if (dst_slot_id >= EQ::invbag::GENERAL_BAGS_BEGIN && dst_slot_id <= EQ::invbag::GENERAL_BAGS_END) {
			dstbag = m_inv.GetItem(EQ::InventoryProfile::CalcSlotId(dst_slot_id));
			if (dstbag)
				dstbagid = dstbag->GetItem()->ID;
		}
		if ((srcbagid && srcbag->GetItem()->BagType == EQ::item::BagTypeTradersSatchel) ||
		    (dstbagid && dstbag->GetItem()->BagType == EQ::item::BagTypeTradersSatchel) ||
		    (srcitemid && src_inst && src_inst->GetItem()->BagType == EQ::item::BagTypeTradersSatchel) ||
		    (dstitemid && dst_inst && dst_inst->GetItem()->BagType == EQ::item::BagTypeTradersSatchel)) {
			TraderEndTrader();
			Message(Chat::Red,"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 < EQ::invslot::WORLD_BEGIN || src_slot_id > EQ::invslot::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)) { LogInventory("Recursive SwapItem call failed due to non-existent destination item (charid: [{}], fromslot: [{}], toslot: [{}])", CharacterID(), src_slot_id, dst_slot_id); }
		}

		return false;
	}
	//verify shared bank transactions in the database
	if (
		src_inst &&
		(
			EQ::ValueWithin(src_slot_id, EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END) ||
			EQ::ValueWithin(src_slot_id, EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END)
		)
	) {
		if(!database.VerifyInventory(account_id, src_slot_id, src_inst)) {
			LogError("Player [{}] on account [{}] was found exploiting the shared bank.\n", GetName(), account_name);
			DeleteItemInInventory(dst_slot_id,0,true);
			return(false);
		}

		if (EQ::ValueWithin(src_slot_id, EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END) && src_inst->IsClassBag()){
			for (uint8 idx = EQ::invbag::SLOT_BEGIN; idx <= EQ::invbag::SLOT_END; idx++) {
				const EQ::ItemInstance* baginst = src_inst->GetItem(idx);
				if (baginst && !database.VerifyInventory(account_id, EQ::InventoryProfile::CalcSlotId(src_slot_id, idx), baginst)){
					DeleteItemInInventory(EQ::InventoryProfile::CalcSlotId(src_slot_id, idx), 0, false);
				}
			}
		}
	}

	if (
		dst_inst &&
		(
			EQ::ValueWithin(dst_slot_id, EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END) ||
			EQ::ValueWithin(dst_slot_id, EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END)
		)
	) {
		if(!database.VerifyInventory(account_id, dst_slot_id, dst_inst)) {
			LogError("Player [{}] on account [{}] was found exploting the shared bank.\n", GetName(), account_name);
			DeleteItemInInventory(src_slot_id,0,true);
			return(false);
		}

		if (EQ::ValueWithin(dst_slot_id, EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END) && dst_inst->IsClassBag()){
			for (uint8 idx = EQ::invbag::SLOT_BEGIN; idx <= EQ::invbag::SLOT_END; idx++) {
				const EQ::ItemInstance* baginst = dst_inst->GetItem(idx);
				if (baginst && !database.VerifyInventory(account_id, EQ::InventoryProfile::CalcSlotId(dst_slot_id, idx), baginst)){
					DeleteItemInInventory(EQ::InventoryProfile::CalcSlotId(dst_slot_id, idx), 0, false);
				}
			}
		}
	}


	// Check for No Drop Hacks
	Mob* with = trade->With();
	if (
		(
			(
				with &&
				with->IsClient() &&
				!with->CastToClient()->IsBecomeNPC() &&
				EQ::ValueWithin(dst_slot_id, EQ::invslot::TRADE_BEGIN, EQ::invslot::TRADE_END)
			) ||
			EQ::ValueWithin(dst_slot_id, EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END) ||
			EQ::ValueWithin(dst_slot_id, EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END)
		) &&
		GetInv().CheckNoDrop(src_slot_id) &&
		!CanTradeFVNoDropItem()
	) {
		auto ndh_inst = m_inv[src_slot_id];
		std::string ndh_item_data;
		if (ndh_inst == nullptr) {
			ndh_item_data.append("[nullptr on EQ::ItemInstance*]");
		}
		else {
			auto ndh_item = ndh_inst->GetItem();
			if (ndh_item == nullptr) {
				ndh_item_data.append("[nullptr on ItemData*]");
			}
			else {
				ndh_item_data.append(StringFormat("name=%s", ndh_item->Name));
				ndh_item_data.append(StringFormat(", id=%u", ndh_item->ID));
				ndh_item_data.append(StringFormat(", nodrop=%s(%u)", (ndh_item->NoDrop == 0 ? "true" : "false"), ndh_item->NoDrop));
			}
		}
		LogError("WorldKick() of Player [{}](id:[{}], acct:[{}]) due to 'NoDrop Hack' detection >> SlotID:[{}], ItemData:[{}]",
			GetName(), CharacterID(), AccountID(), src_slot_id, ndh_item_data.c_str());
		ndh_inst = nullptr;

		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 >= EQ::invslot::WORLD_BEGIN && src_slot_id <= EQ::invslot::WORLD_END) {
			// Picking up item from world container
			EQ::ItemInstance* inst = m_tradeskill_object->PopItem(EQ::InventoryProfile::CalcBagIdx(src_slot_id));
			if (inst) {
				PutItemInInventory(dst_slot_id, *inst, false);
				safe_delete(inst);
			}

			return true;
		}
		else if (dst_slot_id >= EQ::invslot::WORLD_BEGIN && dst_slot_id <= EQ::invslot::WORLD_END) {
			// Putting item into world container, which may swap (or pile onto) with existing item
			uint8 world_idx = EQ::InventoryProfile::CalcBagIdx(dst_slot_id);
			EQ::ItemInstance* 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 EQ::ItemData* world_item = world_inst->GetItem();
				const EQ::ItemData* 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->PutItem(world_idx, world_inst);
						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
						EQ::ItemInstance* 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 == EQ::invslot::slotCursor)
			{
				if (dstitemid == 0)
				{
					SendCursorBuffer();
				}
				auto s = m_inv.cursor_cbegin(), e = m_inv.cursor_cend();
				database.SaveCursor(character_id, s, e);
			}
			else
			{
				database.SaveInventory(character_id, m_inv[src_slot_id], src_slot_id);
			}

			return true;
		}
	}

	// Step 4: Check for entity trade
	if (dst_slot_id >= EQ::invslot::TRADE_BEGIN && dst_slot_id <= EQ::invslot::TRADE_END) {
		if (src_slot_id != EQ::invslot::slotCursor) {
			Kick("Trade with non-cursor item");
			return false;
		}
		if (with) {
			LogInventory("Trade item move from slot [{}] to slot [{}] (trade with [{}])", src_slot_id, dst_slot_id, with->GetName());
			// Fill Trade list with items from cursor
			if (!m_inv[EQ::invslot::slotCursor]) {
				Message(Chat::Red, "Error: Cursor item not located on server!");
				return false;
			}

			trade->AddEntity(dst_slot_id, move_in->number_in_stack);
			if (dstitemid == 0)
			{
				SendCursorBuffer();
			}

			return true;
		} else {
			SummonItem(src_inst->GetID(), src_inst->GetCharges());
			DeleteItemInInventory(EQ::invslot::slotCursor);

			return true;
		}
	}

	bool all_to_stack = false;
	// 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()) {
			LogInventory("Move from [{}] to [{}] with stack size [{}]. [{}] is not a stackable item. (charname: [{}])", 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()) {
				LogInventory("Move from [{}] to [{}] with stack size [{}]. Incompatible item types: [{}] != [{}]", 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.
				LogInventory("Move from [{}] to [{}] with stack size [{}]. dest has [{}]/[{}] 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)
				{
					LogInventory("Dest ([{}]) now has [{}] charges, source ([{}]) was entirely consumed. ([{}] moved)", dst_slot_id, dst_inst->GetCharges(), src_slot_id, usedcharges);
					database.SaveInventory(CharacterID(),nullptr,src_slot_id);
					m_inv.DeleteItem(src_slot_id);
					all_to_stack = true;
				} else {
					LogInventory("Dest ([{}]) now has [{}] charges, source ([{}]) has [{}] ([{}] moved)", dst_slot_id, dst_inst->GetCharges(), src_slot_id, src_inst->GetCharges(), usedcharges);
				}
			} else {
				LogInventory("Move from [{}] to [{}] with stack size [{}]. Exceeds dest maximum stack size: [{}]/[{}]", 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
				EQ::InventoryProfile::SwapItemFailState fail_state = EQ::InventoryProfile::swapInvalid;
				if (!m_inv.SwapItem(src_slot_id, dst_slot_id, fail_state)) { return false; }
				LogInventory("Move entire stack from [{}] to [{}] with stack size [{}]. 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);
				LogInventory("Split stack of [{}] ([{}]) from slot [{}] to [{}] with stack size [{}]. Src keeps [{}]", src_inst->GetItem()->Name, src_inst->GetItem()->ID, src_slot_id, dst_slot_id, move_in->number_in_stack, src_inst->GetCharges());
				EQ::ItemInstance* 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 <= EQ::invslot::EQUIPMENT_END) && dst_slot_id >= EQ::invslot::EQUIPMENT_BEGIN) {
			if (src_inst->GetItem()->Attuneable) {
				src_inst->SetAttuned(true);
			}
			if (src_inst->IsAugmented()) {
				for (int i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) {
					if (src_inst->GetAugment(i)) {
						if (src_inst->GetAugment(i)->GetItem()->Attuneable) {
							src_inst->GetAugment(i)->SetAttuned(true);
						}
					}
				}
			}
			SetMaterial(dst_slot_id,src_inst->GetItem()->ID);
		}

		EQ::InventoryProfile::SwapItemFailState fail_state = EQ::InventoryProfile::swapInvalid;
		if (!m_inv.SwapItem(src_slot_id, dst_slot_id, fail_state, GetBaseRace(), GetBaseClass(), GetDeity(), GetLevel())) {
			const char* fail_message = "The selected slot was invalid.";
			if (fail_state == EQ::InventoryProfile::swapRaceClass || fail_state == EQ::InventoryProfile::swapDeity)
				fail_message = "Your class, deity and/or race may not equip that item.";
			else if (fail_state == EQ::InventoryProfile::swapLevel)
				fail_message = "You are not sufficient level to use this item.";

			if (fail_message)
				Message(Chat::Red, "%s", fail_message);

			return false;
		}

		LogInventory("Moving entire item from slot [{}] to slot [{}]", src_slot_id, dst_slot_id);
		if (src_inst->IsStackable() &&
			dst_slot_id >= EQ::invbag::GENERAL_BAGS_BEGIN &&
			dst_slot_id <= EQ::invbag::GENERAL_BAGS_END
			)	{
			EQ::ItemInstance *bag = nullptr;
			bag = m_inv.GetItem(EQ::InventoryProfile::CalcSlotId(dst_slot_id));
			if (bag) {
				if (bag->GetItem()->BagType == EQ::item::BagTypeTradersSatchel) {
					PutItemInInventory(dst_slot_id, *src_inst, true);
					//This resets the UF client to recognize the new serial item of the placed item
					//if it came from a stack without having to close the trader window and re-open.
					//It is not required for the RoF2 client.
					if (ClientVersion() < EQ::versions::ClientVersion::RoF2) {
						auto outapp  = new EQApplicationPacket(OP_Trader, sizeof(TraderBuy_Struct));
						auto data    = (TraderBuy_Struct *) outapp->pBuffer;
						data->action = BazaarBuyItem;
						FastQueuePacket(&outapp);
					}
				}
			}
		}

		if (src_slot_id <= EQ::invslot::EQUIPMENT_END) {
			if(src_inst) {
				if (parse->ItemHasQuestSub(src_inst, EVENT_UNEQUIP_ITEM)) {
					parse->EventItem(EVENT_UNEQUIP_ITEM, this, src_inst, nullptr, "", src_slot_id);
				}

				if (parse->PlayerHasQuestSub(EVENT_UNEQUIP_ITEM_CLIENT)) {
					const auto& export_string = fmt::format(
						"{} {}",
						src_inst->IsStackable() ? src_inst->GetCharges() : 1,
						src_slot_id
					);

					parse->EventPlayer(EVENT_UNEQUIP_ITEM_CLIENT, this, export_string, src_inst->GetItem()->ID);
				}
			}

			if (dst_inst) {
				if (parse->ItemHasQuestSub(dst_inst, EVENT_EQUIP_ITEM)) {
					parse->EventItem(EVENT_EQUIP_ITEM, this, dst_inst, nullptr, "", src_slot_id);
				}

				if (parse->PlayerHasQuestSub(EVENT_EQUIP_ITEM_CLIENT)) {
					const auto& export_string = fmt::format(
						"{} {}",
						dst_inst->IsStackable() ? dst_inst->GetCharges() : 1,
						src_slot_id
					);

					parse->EventPlayer(EVENT_EQUIP_ITEM_CLIENT, this, export_string, dst_inst->GetItem()->ID);
				}
			}
		}

		if (dst_slot_id <= EQ::invslot::EQUIPMENT_END) {
			if (dst_inst) {
				if (parse->ItemHasQuestSub(dst_inst, EVENT_UNEQUIP_ITEM)) {
					parse->EventItem(EVENT_UNEQUIP_ITEM, this, dst_inst, nullptr, "", dst_slot_id);
				}

				if (parse->PlayerHasQuestSub(EVENT_UNEQUIP_ITEM_CLIENT)) {
					const auto& export_string = fmt::format(
						"{} {}",
						dst_inst->IsStackable() ? dst_inst->GetCharges() : 1,
						dst_slot_id
					);

					std::vector<std::any> args = { dst_inst };

					parse->EventPlayer(EVENT_UNEQUIP_ITEM_CLIENT, this, export_string, dst_inst->GetItem()->ID, &args);
				}
			}

			if (src_inst) {
				if (parse->ItemHasQuestSub(src_inst, EVENT_EQUIP_ITEM)) {
					parse->EventItem(EVENT_EQUIP_ITEM, this, src_inst, nullptr, "", dst_slot_id);
				}

				if (parse->PlayerHasQuestSub(EVENT_EQUIP_ITEM_CLIENT)) {
					const auto& export_string = fmt::format(
						"{} {}",
						src_inst->IsStackable() ? src_inst->GetCharges() : 1,
						dst_slot_id
					);

					std::vector<std::any> args = { src_inst };

					parse->EventPlayer(EVENT_EQUIP_ITEM_CLIENT, this, export_string, src_inst->GetItem()->ID, &args);
				}
			}
		}
	}

	int matslot = SlotConvert2(dst_slot_id);
	if (dst_slot_id <= EQ::invslot::EQUIPMENT_END) {// on Titanium and ROF2 /showhelm works even if sending helm slot
		SendWearChange(matslot);
	}

	// Step 7: Save change to the database
	if (src_slot_id == EQ::invslot::slotCursor) {
		// If not swapping another item to cursor and stacking items were depleted
		if (dstitemid == 0 || all_to_stack == true)
		{
			SendCursorBuffer();
		}
		auto s = m_inv.cursor_cbegin(), e = m_inv.cursor_cend();
		database.SaveCursor(character_id, s, e);
	}
	else {
		database.SaveInventory(character_id, m_inv.GetItem(src_slot_id), src_slot_id);
	}

	if (dst_slot_id == EQ::invslot::slotCursor) {
		auto s = m_inv.cursor_cbegin(), e = m_inv.cursor_cend();
		database.SaveCursor(character_id, s, e);
	}
	else {
		database.SaveInventory(character_id, m_inv.GetItem(dst_slot_id), dst_slot_id);
	}

	// Step 8: Re-calc stats
	CalcBonuses();
	ApplyWeaponsStance();
	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
	LogInventory("Inventory desyncronization. (charname: [{}], source: [{}], destination: [{}])", GetName(), move_slots->from_slot, move_slots->to_slot);
	Message(Chat::Yellow, "Inventory Desyncronization detected: Resending slot data...");

	if (move_slots->from_slot >= EQ::invslot::EQUIPMENT_BEGIN && move_slots->from_slot <= EQ::invbag::CURSOR_BAG_END) {
		int16 resync_slot = (EQ::InventoryProfile::CalcSlotId(move_slots->from_slot) == INVALID_INDEX) ? move_slots->from_slot : EQ::InventoryProfile::CalcSlotId(move_slots->from_slot);
		if (IsValidSlot(resync_slot) && resync_slot != INVALID_INDEX) {
			// This prevents the client from crashing when closing any 'phantom' bags
			const EQ::ItemData* token_struct = database.GetItem(22292); // 'Copper Coin'
			EQ::ItemInstance* 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 {
				auto 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(Chat::Lime, "Source slot %i resyncronized.", move_slots->from_slot);
		}
		else { Message(Chat::Red, "Could not resyncronize source slot %i.", move_slots->from_slot); }
	}
	else {
		int16 resync_slot = (EQ::InventoryProfile::CalcSlotId(move_slots->from_slot) == INVALID_INDEX) ? move_slots->from_slot : EQ::InventoryProfile::CalcSlotId(move_slots->from_slot);
		if (IsValidSlot(resync_slot) && resync_slot != INVALID_INDEX) {
			if(m_inv[resync_slot]) {
				const EQ::ItemData* token_struct = database.GetItem(22292); // 'Copper Coin'
				EQ::ItemInstance* 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(Chat::Lime, "Source slot %i resyncronized.", move_slots->from_slot);
			}
			else { Message(Chat::Red, "Could not resyncronize source slot %i.", move_slots->from_slot); }
		}
		else { Message(Chat::Red, "Could not resyncronize source slot %i.", move_slots->from_slot); }
	}

	if (move_slots->to_slot >= EQ::invslot::EQUIPMENT_BEGIN && move_slots->to_slot <= EQ::invbag::CURSOR_BAG_END) {
		int16 resync_slot = (EQ::InventoryProfile::CalcSlotId(move_slots->to_slot) == INVALID_INDEX) ? move_slots->to_slot : EQ::InventoryProfile::CalcSlotId(move_slots->to_slot);
		if (IsValidSlot(resync_slot) && resync_slot != INVALID_INDEX) {
			const EQ::ItemData* token_struct = database.GetItem(22292); // 'Copper Coin'
			EQ::ItemInstance* 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 {
				auto 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(Chat::Lime, "Destination slot %i resyncronized.", move_slots->to_slot);
		}
		else { Message(Chat::Red, "Could not resyncronize destination slot %i.", move_slots->to_slot); }
	}
	else {
		int16 resync_slot = (EQ::InventoryProfile::CalcSlotId(move_slots->to_slot) == INVALID_INDEX) ? move_slots->to_slot : EQ::InventoryProfile::CalcSlotId(move_slots->to_slot);
		if (IsValidSlot(resync_slot) && resync_slot != INVALID_INDEX) {
			if(m_inv[resync_slot]) {
				const EQ::ItemData* token_struct = database.GetItem(22292); // 'Copper Coin'
				EQ::ItemInstance* 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(Chat::Lime, "Destination slot %i resyncronized.", move_slots->to_slot);
			}
			else { Message(Chat::Red, "Could not resyncronize destination slot %i.", move_slots->to_slot); }
		}
		else { Message(Chat::Red, "Could not resyncronize destination slot %i.", move_slots->to_slot); }
	}
}

void Client::DyeArmor(EQ::TintProfile* dye){
	int16 slot=0;
	for (int i = EQ::textures::textureBegin; i <= EQ::textures::LastTintableTexture; i++) {
		if ((m_pp.item_tint.Slot[i].Color & 0x00FFFFFF) != (dye->Slot[i].Color & 0x00FFFFFF)) {
			slot = m_inv.HasItem(32557, 1, invWherePersonal);
			if (slot != INVALID_INDEX){
				DeleteItemInInventory(slot,1,true);
				uint8 slot2=SlotConvert(i);
				EQ::ItemInstance* inst = m_inv.GetItem(slot2);
				if(inst){
					uint32 armor_color = ((uint32)dye->Slot[i].Red << 16) | ((uint32)dye->Slot[i].Green << 8) | ((uint32)dye->Slot[i].Blue);
					inst->SetColor(armor_color);
					database.SaveCharacterMaterialColor(CharacterID(), i, armor_color);
					database.SaveInventory(CharacterID(),inst,slot2);
					if(dye->Slot[i].UseTint)
						m_pp.item_tint.Slot[i].UseTint = 0xFF;
					else
						m_pp.item_tint.Slot[i].UseTint=0x00;
				}
				m_pp.item_tint.Slot[i].Blue=dye->Slot[i].Blue;
				m_pp.item_tint.Slot[i].Red=dye->Slot[i].Red;
				m_pp.item_tint.Slot[i].Green=dye->Slot[i].Green;
				SendWearChange(i);
			}
			else{
				Message(Chat::Red,"Could not locate A Vial of Prismatic Dye.");
				return;
			}
		}
	}
	auto outapp = new EQApplicationPacket(OP_Dye, 0);
	QueuePacket(outapp);
	safe_delete(outapp);

}

void Client::DyeArmorBySlot(uint8 slot, uint8 red, uint8 green, uint8 blue, uint8 use_tint) {
	uint8 item_slot = SlotConvert(slot);
	EQ::ItemInstance* item_instance = m_inv.GetItem(item_slot);
	if (item_instance) {
		uint32 armor_color = ((uint32)red << 16) | ((uint32)green << 8) | ((uint32)blue);
		item_instance->SetColor(armor_color);
		database.SaveCharacterMaterialColor(CharacterID(), slot, armor_color);
		database.SaveInventory(CharacterID(), item_instance, item_slot);
		m_pp.item_tint.Slot[slot].UseTint = (use_tint ? 0xFF : 0x00);
	}
	m_pp.item_tint.Slot[slot].Red = red;
	m_pp.item_tint.Slot[slot].Green = green;
	m_pp.item_tint.Slot[slot].Blue = blue;
	SendWearChange(slot);
}

bool Client::DecreaseByID(uint32 type, int16 quantity) {
	const EQ::ItemData* TempItem = nullptr;
	EQ::ItemInstance* ins = nullptr;
	int x;
	int num = 0;

	for (x = EQ::invslot::POSSESSIONS_BEGIN; x <= EQ::invslot::POSSESSIONS_END; ++x) {
		if (num >= quantity)
			break;
		if ((((uint64)1 << x) & GetInv().GetLookup()->PossessionsBitmask) == 0)
			continue;

		TempItem = nullptr;
		ins = GetInv().GetItem(x);
		if (ins)
			TempItem = ins->GetItem();
		if (TempItem && TempItem->ID == type)
			num += ins->GetCharges();
	}

	for (x = EQ::invbag::GENERAL_BAGS_BEGIN; x <= EQ::invbag::GENERAL_BAGS_END; ++x) {
		if (num >= quantity)
			break;
		if ((((uint64)1 << (EQ::invslot::GENERAL_BEGIN + ((x - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT))) & GetInv().GetLookup()->PossessionsBitmask) == 0)
			continue;

		TempItem = nullptr;
		ins = GetInv().GetItem(x);
		if (ins)
			TempItem = ins->GetItem();
		if (TempItem && TempItem->ID == type)
			num += ins->GetCharges();
	}

	for (x = EQ::invbag::CURSOR_BAG_BEGIN; x <= EQ::invbag::CURSOR_BAG_END; ++x) {
		if (num >= quantity)
			break;

		TempItem = nullptr;
		ins = GetInv().GetItem(x);
		if (ins)
			TempItem = ins->GetItem();
		if (TempItem && TempItem->ID == type)
			num += ins->GetCharges();
	}

	if (num < quantity)
		return false;


	for (x = EQ::invslot::POSSESSIONS_BEGIN; x <= EQ::invslot::POSSESSIONS_END; ++x) {
		if (quantity < 1)
			break;
		if ((((uint64)1 << x) & GetInv().GetLookup()->PossessionsBitmask) == 0)
			continue;

		TempItem = nullptr;
		ins = GetInv().GetItem(x);
		if (ins)
			TempItem = ins->GetItem();
		if (TempItem && TempItem->ID != type)
			continue;

		if (ins->GetCharges() < quantity) {
			quantity -= ins->GetCharges();
			DeleteItemInInventory(x, quantity, true);
		}
		else {
			DeleteItemInInventory(x, quantity, true);
			quantity = 0;
		}
	}

	for (x = EQ::invbag::GENERAL_BAGS_BEGIN; x <= EQ::invbag::GENERAL_BAGS_END; ++x) {
		if (quantity < 1)
			break;
		if ((((uint64)1 << (EQ::invslot::GENERAL_BEGIN + ((x - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT))) & GetInv().GetLookup()->PossessionsBitmask) == 0)
			continue;

		TempItem = nullptr;
		ins = GetInv().GetItem(x);
		if (ins)
			TempItem = ins->GetItem();
		if (TempItem && TempItem->ID != type)
			continue;

		if (ins->GetCharges() < quantity) {
			quantity -= ins->GetCharges();
			DeleteItemInInventory(x, quantity, true);
		}
		else {
			DeleteItemInInventory(x, quantity, true);
			quantity = 0;
		}
	}

	for (x = EQ::invbag::CURSOR_BAG_BEGIN; x <= EQ::invbag::CURSOR_BAG_END; ++x) {
		if (quantity < 1)
			break;

		TempItem = nullptr;
		ins = GetInv().GetItem(x);
		if (ins)
			TempItem = ins->GetItem();
		if (TempItem && TempItem->ID != type)
			continue;

		if (ins->GetCharges() < quantity) {
			quantity -= ins->GetCharges();
			DeleteItemInInventory(x, quantity, true);
		}
		else {
			DeleteItemInInventory(x, quantity, true);
			quantity = 0;
		}
	}

	return true;
}

static bool IsSummonedBagID(uint32 item_id)
{
	switch (item_id) {
	case 17147: // "Spiritual Prismatic Pack"
	case 17303: // "Spirit Pouch"
	case 17304: // "Dimensional Pocket"
	case 17305: // "Dimensional Hole"
	case 17306: // "Glowing Backpack"
	case 17307: // "Quiver of Marr"
	case 17308: // "Bandoleer of Luclin"
	case 17309: // "Pouch of Quellious"
	case 17310: // "Phantom Satchel"
	case 17510: // "Glowing Chest"
	case 17900: // "Grandmaster's Satchel"
	case 57260: // "Glowing Backpack"
	case 57261: // "Pouch of Quellious"
	case 57262: // "Phantom Satchel"
	case 60224: // "Faded-Glyph Tablet"
	case 95199: // "Beginner Artisan Satchel"
	case 95200: // "Apprentice Artisan Satchel"
	case 95201: // "Freshman Artisan Satchel"
	case 95202: // "Journeyman Artisan Satchel"
	case 95203: // "Expert Artisan Satchel"
	case 95204: // "Master Artisan Satchel"
	//case 96960: // "Artisan Satchel" - no 12-slot disenchanted bags
		return true;
	default:
		return false;
	}
}

static uint32 GetDisenchantedBagID(uint8 bag_slots)
{
	switch (bag_slots) {
	case 4:
		return 77772; // "Small Disenchanted Backpack"
	case 6:
		return 77774; // "Disenchanted Backpack"
	case 8:
		return 77776; // "Large Disenchanted Backpack"
	case 10:
		return 77778; // "Huge Disenchanted Backpack"
	default:
		return 0; // no suitable conversions
	}
}

static bool CopyBagContents(EQ::ItemInstance* new_bag, const EQ::ItemInstance* old_bag)
{
	if (!new_bag || !old_bag) { return false; }
	if (new_bag->GetItem()->BagSlots < old_bag->GetItem()->BagSlots) { return false; }

	// pre-check for size comparisons
	for (auto bag_slot = 0; bag_slot < old_bag->GetItem()->BagSlots; ++bag_slot) {
		if (!old_bag->GetItem(bag_slot)) { continue; }
		if (old_bag->GetItem(bag_slot)->GetItem()->Size > new_bag->GetItem()->BagSize) {
			LogInventory("Copy Bag Contents: Failure due to [{}] is larger than size capacity of [{}] ([{}] > [{}])",
				old_bag->GetItem(bag_slot)->GetItem()->Name, new_bag->GetItem()->Name, old_bag->GetItem(bag_slot)->GetItem()->Size, new_bag->GetItem()->BagSize);
			return false;
		}
	}

	for (auto bag_slot = 0; bag_slot < old_bag->GetItem()->BagSlots; ++bag_slot) {
		if (!old_bag->GetItem(bag_slot)) { continue; }
		new_bag->PutItem(bag_slot, *(old_bag->GetItem(bag_slot)));
	}

	return true;
}

void Client::DisenchantSummonedBags(bool client_update)
{
	for (auto slot_id = EQ::invslot::GENERAL_BEGIN; slot_id <= EQ::invslot::GENERAL_END; ++slot_id) {
		if ((((uint64)1 << slot_id) & GetInv().GetLookup()->PossessionsBitmask) == 0)
			continue; // not usable this session - will be disenchanted once player logs in on client that doesn't exclude affected slots

		auto inst = m_inv[slot_id];
		if (!inst) { continue; }
		if (!IsSummonedBagID(inst->GetItem()->ID)) { continue; }
		if (!inst->GetItem()->IsClassBag()) { continue; }
		if (inst->GetTotalItemCount() == 1) { continue; }

		auto new_id = GetDisenchantedBagID(inst->GetItem()->BagSlots);
		if (!new_id) { continue; }
		auto new_item = database.GetItem(new_id);
		if (!new_item) { continue; }
		auto new_inst = database.CreateBaseItem(new_item);
		if (!new_inst) { continue; }

		if (CopyBagContents(new_inst, inst)) {
			LogInventory("Disenchant Summoned Bags: Replacing [{}] with [{}] in slot [{}]", inst->GetItem()->Name, new_inst->GetItem()->Name, slot_id);
			PutItemInInventory(slot_id, *new_inst, client_update);
		}
		safe_delete(new_inst);
	}

	for (auto slot_id = EQ::invslot::BANK_BEGIN; slot_id <= EQ::invslot::BANK_END; ++slot_id) {
		if ((slot_id - EQ::invslot::BANK_BEGIN) >= GetInv().GetLookup()->InventoryTypeSize.Bank)
			continue;

		auto inst = m_inv[slot_id];
		if (!inst) { continue; }
		if (!IsSummonedBagID(inst->GetItem()->ID)) { continue; }
		if (!inst->GetItem()->IsClassBag()) { continue; }
		if (inst->GetTotalItemCount() == 1) { continue; }

		auto new_id = GetDisenchantedBagID(inst->GetItem()->BagSlots);
		if (!new_id) { continue; }
		auto new_item = database.GetItem(new_id);
		if (!new_item) { continue; }
		auto new_inst = database.CreateBaseItem(new_item);
		if (!new_inst) { continue; }

		if (CopyBagContents(new_inst, inst)) {
			LogInventory("Disenchant Summoned Bags: Replacing [{}] with [{}] in slot [{}]", inst->GetItem()->Name, new_inst->GetItem()->Name, slot_id);
			PutItemInInventory(slot_id, *new_inst, client_update);
		}
		safe_delete(new_inst);
	}

	for (auto slot_id = EQ::invslot::SHARED_BANK_BEGIN; slot_id <= EQ::invslot::SHARED_BANK_END; ++slot_id) {
		auto inst = m_inv[slot_id];
		if (!inst) { continue; }
		if (!IsSummonedBagID(inst->GetItem()->ID)) { continue; }
		if (!inst->GetItem()->IsClassBag()) { continue; }
		if (inst->GetTotalItemCount() == 1) { continue; }

		auto new_id = GetDisenchantedBagID(inst->GetItem()->BagSlots);
		if (!new_id) { continue; }
		auto new_item = database.GetItem(new_id);
		if (!new_item) { continue; }
		auto new_inst = database.CreateBaseItem(new_item);
		if (!new_inst) { continue; }

		if (CopyBagContents(new_inst, inst)) {
			LogInventory("Disenchant Summoned Bags: Replacing [{}] with [{}] in slot [{}]", inst->GetItem()->Name, new_inst->GetItem()->Name, slot_id);
			PutItemInInventory(slot_id, *new_inst, client_update);
		}
		safe_delete(new_inst);
	}

	while (!m_inv.CursorEmpty()) {
		auto inst = m_inv[EQ::invslot::slotCursor];
		if (!inst) { break; }
		if (!IsSummonedBagID(inst->GetItem()->ID)) { break; }
		if (!inst->GetItem()->IsClassBag()) { break; }
		if (inst->GetTotalItemCount() == 1) { break; }

		auto new_id = GetDisenchantedBagID(inst->GetItem()->BagSlots);
		if (!new_id) { break; }
		auto new_item = database.GetItem(new_id);
		if (!new_item) { break; }
		auto new_inst = database.CreateBaseItem(new_item);
		if (!new_inst) { break; }

		if (CopyBagContents(new_inst, inst)) {
			LogInventory("Disenchant Summoned Bags: Replacing [{}] with [{}] in slot [{}]", inst->GetItem()->Name, new_inst->GetItem()->Name, EQ::invslot::slotCursor);
			std::list<EQ::ItemInstance*> local;
			local.push_front(new_inst);
			m_inv.PopItem(EQ::invslot::slotCursor);
			safe_delete(inst);

			while (!m_inv.CursorEmpty()) {
				auto limbo_inst = m_inv.PopItem(EQ::invslot::slotCursor);
				if (limbo_inst == nullptr) { continue; }
				local.push_back(limbo_inst);
			}

			for (auto iter = local.begin(); iter != local.end(); ++iter) {
				auto cur_inst = *iter;
				if (cur_inst == nullptr) { continue; }
				m_inv.PushCursor(*cur_inst);
				safe_delete(cur_inst);
			}
			local.clear();

			auto s = m_inv.cursor_cbegin(), e = m_inv.cursor_cend();
			database.SaveCursor(CharacterID(), s, e);
		}
		else {
			safe_delete(new_inst); // deletes disenchanted bag if not used
		}

		break;
	}
}

void Client::RemoveNoRent(bool client_update)
{
	for (auto slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; ++slot_id) {
		if ((((uint64)1 << slot_id) & GetInv().GetLookup()->PossessionsBitmask) == 0)
			continue;

		auto inst = m_inv[slot_id];
		if(inst && !inst->GetItem()->NoRent) {
			LogInventory("NoRent Timer Lapse: Deleting [{}] from slot [{}]", inst->GetItem()->Name, slot_id);
			DeleteItemInInventory(slot_id, 0, client_update);
		}
	}

	for (auto slot_id = EQ::invslot::GENERAL_BEGIN; slot_id <= EQ::invslot::GENERAL_END; ++slot_id) {
		if ((((uint64)1 << slot_id) & GetInv().GetLookup()->PossessionsBitmask) == 0)
			continue;

		auto inst = m_inv[slot_id];
		if (inst && !inst->GetItem()->NoRent) {
			LogInventory("NoRent Timer Lapse: Deleting [{}] from slot [{}]", inst->GetItem()->Name, slot_id);
			DeleteItemInInventory(slot_id, 0, client_update);
		}
	}

	for (auto slot_id = EQ::invbag::GENERAL_BAGS_BEGIN; slot_id <= EQ::invbag::CURSOR_BAG_END; ++slot_id) {
		auto temp_slot = EQ::invslot::GENERAL_BEGIN + ((slot_id - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT);
		if ((((uint64)1 << temp_slot) & GetInv().GetLookup()->PossessionsBitmask) == 0)
			continue;

		auto inst = m_inv[slot_id];
		if(inst && !inst->GetItem()->NoRent) {
			LogInventory("NoRent Timer Lapse: Deleting [{}] from slot [{}]", inst->GetItem()->Name, slot_id);
			DeleteItemInInventory(slot_id, 0, client_update);
		}
	}

	for (auto slot_id = EQ::invslot::BANK_BEGIN; slot_id <= EQ::invslot::BANK_END; ++slot_id) {
		if ((slot_id - EQ::invslot::BANK_BEGIN) >= GetInv().GetLookup()->InventoryTypeSize.Bank)
			continue;

		auto inst = m_inv[slot_id];
		if(inst && !inst->GetItem()->NoRent) {
			LogInventory("NoRent Timer Lapse: Deleting [{}] from slot [{}]", inst->GetItem()->Name, slot_id);
			DeleteItemInInventory(slot_id); // Can't delete from client Bank slots
		}
	}

	for (auto slot_id = EQ::invbag::BANK_BAGS_BEGIN; slot_id <= EQ::invbag::BANK_BAGS_END; ++slot_id) {
		auto temp_slot = (slot_id - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT;
		if (temp_slot >= GetInv().GetLookup()->InventoryTypeSize.Bank)
			continue;

		auto inst = m_inv[slot_id];
		if(inst && !inst->GetItem()->NoRent) {
			LogInventory("NoRent Timer Lapse: Deleting [{}] from slot [{}]", inst->GetItem()->Name, slot_id);
			DeleteItemInInventory(slot_id); // Can't delete from client Bank Container slots
		}
	}

	for (auto slot_id = EQ::invslot::SHARED_BANK_BEGIN; slot_id <= EQ::invslot::SHARED_BANK_END; ++slot_id) {
		auto inst = m_inv[slot_id];
		if(inst && !inst->GetItem()->NoRent) {
			LogInventory("NoRent Timer Lapse: Deleting [{}] from slot [{}]", inst->GetItem()->Name, slot_id);
			DeleteItemInInventory(slot_id); // Can't delete from client Shared Bank slots
		}
	}

	for (auto slot_id = EQ::invbag::SHARED_BANK_BAGS_BEGIN; slot_id <= EQ::invbag::SHARED_BANK_BAGS_END; ++slot_id) {
		auto inst = m_inv[slot_id];
		if(inst && !inst->GetItem()->NoRent) {
			LogInventory("NoRent Timer Lapse: Deleting [{}] from slot [{}]", inst->GetItem()->Name, slot_id);
			DeleteItemInInventory(slot_id); // Can't delete from client Shared Bank Container slots
		}
	}

	if (!m_inv.CursorEmpty()) {
		std::list<EQ::ItemInstance*> local;

		while (!m_inv.CursorEmpty()) {
			auto inst = m_inv.PopItem(EQ::invslot::slotCursor);
			if (inst == nullptr) { continue; }
			local.push_back(inst);
		}

		for (auto iter = local.begin(); iter != local.end(); ++iter) {
			auto inst = *iter;
			if (inst == nullptr) { continue; }
			if (!inst->GetItem()->NoRent) {
				LogInventory("NoRent Timer Lapse: Deleting [{}] from `Limbo`", inst->GetItem()->Name);
			}
			else {
				m_inv.PushCursor(*inst);
			}
			safe_delete(inst);
		}
		local.clear();

		auto s = m_inv.cursor_cbegin(), e = m_inv.cursor_cend();
		database.SaveCursor(CharacterID(), s, e);
	}
}

// Two new methods to alleviate perpetual login desyncs
void Client::RemoveDuplicateLore()
{
	for (auto slot_id : GetInventorySlots()) {
		if ((((uint64) 1 << slot_id) & GetInv().GetLookup()->PossessionsBitmask) == 0) {
			continue;
		}

		// ignore shared bank slots
		if (slot_id >= EQ::invslot::SHARED_BANK_BEGIN && slot_id <= EQ::invslot::SHARED_BANK_END) {
			continue;
		}

		if (slot_id >= EQ::invbag::SHARED_BANK_BAGS_BEGIN && slot_id <= EQ::invbag::SHARED_BANK_BAGS_END) {
			continue;
		}

		// slot gets handled in a queue
		if (slot_id == EQ::invslot::slotCursor) {
			continue;
		}

		// temporarily move the item off of the slot
		auto inst = m_inv.PopItem(slot_id);
		if (!inst) {
			continue;
		}

		if (CheckLoreConflict(inst->GetItem())) {
			LogError(
				"Lore Duplication Error | Deleting [{}] ({}) from slot [{}] client [{}]",
				inst->GetItem()->Name,
				inst->GetItem()->ID,
				slot_id,
				GetCleanName()
			);
			database.SaveInventory(character_id, nullptr, slot_id);
			safe_delete(inst);
		}

		// if no lore conflict, put the item back in the slot
		m_inv.PushItem(slot_id, inst);
	}

	if (!m_inv.CursorEmpty()) {
		std::list<EQ::ItemInstance*> local_1;
		std::list<EQ::ItemInstance*> local_2;

		while (!m_inv.CursorEmpty()) {
			auto inst = m_inv.PopItem(EQ::invslot::slotCursor);
			if (!inst) {
				continue;
			}
			local_1.push_back(inst);
		}

		for (auto inst: local_1) {
			if (!inst) {
				continue;
			}
			if (CheckLoreConflict(inst->GetItem())) {
				LogError(
					"Lore Duplication Error | Deleting [{}] ({}) from `Limbo` client [{}]",
					inst->GetItem()->Name,
					inst->GetItem()->ID,
					GetCleanName()
				);
				safe_delete(inst);
			}
			else {
				local_2.push_back(inst);
			}
		}
		local_1.clear();

		for (auto inst: local_2) {
			if (!inst) {
				continue;
			}
			if (!inst->GetItem()->LoreFlag ||
				((inst->GetItem()->LoreGroup == -1) &&
				 (m_inv.HasItem(inst->GetID(), 0, invWhereCursor) == INVALID_INDEX)) ||
				(inst->GetItem()->LoreGroup && (~inst->GetItem()->LoreGroup) &&
				 (m_inv.HasItemByLoreGroup(inst->GetItem()->LoreGroup, invWhereCursor) == INVALID_INDEX))
				) {
				m_inv.PushCursor(*inst);
			}
			else {
				LogError(
					"Lore Duplication Error | Deleting [{}] ({}) from `Limbo` client [{}]",
					inst->GetItem()->Name,
					inst->GetItem()->ID,
					GetCleanName()
				);
			}
			safe_delete(inst);
		}
		local_2.clear();

		auto s = m_inv.cursor_cbegin(), e = m_inv.cursor_cend();
		database.SaveCursor(CharacterID(), s, e);
	}
}

void Client::MoveSlotNotAllowed(bool client_update)
{
	for (auto slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; ++slot_id) {
		if(m_inv[slot_id] && !m_inv[slot_id]->IsSlotAllowed(slot_id)) {
			auto inst = m_inv.PopItem(slot_id);
			bool is_arrow = (inst->GetItem()->ItemType == EQ::item::ItemTypeArrow) ? true : false;
			int16 free_slot_id = m_inv.FindFreeSlot(inst->IsClassBag(), true, inst->GetItem()->Size, is_arrow);
			LogInventory("Slot Assignment Error: Moving [{}] from slot [{}] to [{}]", 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);
		}
	}

	// added this check to move any client-based excluded slots
	//for (auto slot_id = EQ::invslot::POSSESSIONS_BEGIN; slot_id <= EQ::invslot::POSSESSIONS_END; ++slot_id) {
	//	if (((uint64)1 << slot_id) & GetInv().GetLookup()->PossessionsBitmask != 0)
	//		continue;

	//	if (m_inv[slot_id]) { // this is currently dangerous for bag-based movements since limbo does not save bag slots
	//		auto inst = m_inv.PopItem(slot_id);
	//		bool is_arrow = (inst->GetItem()->ItemType == EQ::item::ItemTypeArrow) ? true : false;
	//		int16 free_slot_id = m_inv.FindFreeSlot(inst->IsClassBag(), true, inst->GetItem()->Size, is_arrow);
	//		LogInventory("Slot Assignment Error: Moving [{}] from slot [{}] to [{}]", 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);
	//	}
	//}

	// No need to check inventory, cursor, bank or shared bank since they allow max item size and containers
	// 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::GetEquippedItemFromTextureSlot(uint8 material_slot) const
{
	int16 inventory_slot;

	const EQ::ItemInstance *item;

	if (material_slot > EQ::textures::LastTexture) {
		return 0;
	}

	inventory_slot = EQ::InventoryProfile::CalcSlotFromMaterial(material_slot);
	if (inventory_slot == INVALID_INDEX) {
		return 0;
	}

	item = m_inv.GetItem(inventory_slot);

	if (item && item->GetItem()) {
		return item->GetItem()->ID;
	}

	return 0;
}

uint32 Client::GetEquipmentColor(uint8 material_slot) const
{
	if (material_slot > EQ::textures::LastTexture)
		return 0;

	const EQ::ItemData *item = database.GetItem(GetEquippedItemFromTextureSlot(material_slot));
	if(item != nullptr)
		return ((m_pp.item_tint.Slot[material_slot].UseTint) ? m_pp.item_tint.Slot[material_slot].Color : item->Color);

	return 0;
}

// Send an item packet (including all subitems of the item)
void Client::SendItemPacket(int16 slot_id, const EQ::ItemInstance* inst, ItemPacketType packet_type)
{
	if (!inst) {
		return;
	}

	if (!eqs) {
		return;
	}

	if (packet_type != ItemPacketMerchant) {
		if (slot_id <= EQ::invslot::POSSESSIONS_END && slot_id >= EQ::invslot::POSSESSIONS_BEGIN) {
			if ((((uint64)1 << slot_id) & GetInv().GetLookup()->PossessionsBitmask) == 0) {
				LogError("Item not sent to merchant : slot [{}]", slot_id);
				return;
			}
		}
		else if (slot_id <= EQ::invbag::GENERAL_BAGS_END && slot_id >= EQ::invbag::GENERAL_BAGS_BEGIN) {
			auto temp_slot = EQ::invslot::GENERAL_BEGIN + ((slot_id - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT);
			if ((((uint64)1 << temp_slot) & GetInv().GetLookup()->PossessionsBitmask) == 0) {
				LogError("Item not sent to merchant2 : slot [{}]", slot_id);
				return;
			}
		}
		else if (slot_id <= EQ::invslot::BANK_END && slot_id >= EQ::invslot::BANK_BEGIN) {
			if ((slot_id - EQ::invslot::BANK_BEGIN) >= GetInv().GetLookup()->InventoryTypeSize.Bank) {
				LogError("Item not sent to merchant3 : slot [{}]", slot_id);
				return;
			}
		}
		else if (slot_id <= EQ::invbag::BANK_BAGS_END && slot_id >= EQ::invbag::BANK_BAGS_BEGIN) {
			auto temp_slot = (slot_id - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT;
			if (temp_slot >= GetInv().GetLookup()->InventoryTypeSize.Bank) {
				LogError("Item not sent to merchant4 : slot [{}]", slot_id);
				return;
			}
		}
	}

	// Serialize item into |-delimited string (Titanium- uses '|' delimiter .. newer clients use pure data serialization)
	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);
}

static int16 BandolierSlotToWeaponSlot(int BandolierSlot)
{
	switch (BandolierSlot)
	{
	case bandolierPrimary:
		return EQ::invslot::slotPrimary;
	case bandolierSecondary:
		return EQ::invslot::slotSecondary;
	case bandolierRange:
		return EQ::invslot::slotRange;
	default:
		return EQ::invslot::slotAmmo;
	}
}

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;

	LogInventory("Char: [{}] Creating Bandolier Set [{}], Set Name: [{}]", GetName(), bs->Number, bs->Name);
	strcpy(m_pp.bandoliers[bs->Number].Name, bs->Name);

	const EQ::ItemInstance* InvItem = nullptr;
	const EQ::ItemData *BaseItem = nullptr;
	int16 WeaponSlot = 0;

	database.DeleteCharacterBandolier(CharacterID(), bs->Number);

	for(int BandolierSlot = bandolierPrimary; BandolierSlot <= bandolierAmmo; BandolierSlot++) {
		WeaponSlot = BandolierSlotToWeaponSlot(BandolierSlot);
		InvItem = GetInv()[WeaponSlot];
		if(InvItem) {
			BaseItem = InvItem->GetItem();
			LogInventory("Char: [{}] adding item [{}] to slot [{}]", GetName(),BaseItem->Name, WeaponSlot);
			m_pp.bandoliers[bs->Number].Items[BandolierSlot].ID = BaseItem->ID;
			m_pp.bandoliers[bs->Number].Items[BandolierSlot].Icon = BaseItem->Icon;
			strncpy(m_pp.bandoliers[bs->Number].Items[BandolierSlot].Name, BaseItem->Name, sizeof(m_pp.bandoliers[bs->Number].Items[BandolierSlot].Name));
			database.SaveCharacterBandolier(CharacterID(), bs->Number, BandolierSlot, m_pp.bandoliers[bs->Number].Items[BandolierSlot].ID, m_pp.bandoliers[bs->Number].Items[BandolierSlot].Icon, bs->Name);
		}
		else {
			LogInventory("Char: [{}] no item in slot [{}]", GetName(), WeaponSlot);
			m_pp.bandoliers[bs->Number].Items[BandolierSlot].ID = 0;
			m_pp.bandoliers[bs->Number].Items[BandolierSlot].Icon = 0;
			m_pp.bandoliers[bs->Number].Items[BandolierSlot].Name[0] = '\0';
		}
	}
}

void Client::RemoveBandolier(const EQApplicationPacket *app)
{
	BandolierDelete_Struct *bds = (BandolierDelete_Struct*)app->pBuffer;
	LogInventory("Char: [{}] removing set", GetName(), bds->Number);
	memset(m_pp.bandoliers[bds->Number].Name, 0, 32);
	for(int i = bandolierPrimary; i <= bandolierAmmo; i++) {
		m_pp.bandoliers[bds->Number].Items[i].ID = 0;
		m_pp.bandoliers[bds->Number].Items[i].Icon = 0;
	}
	database.DeleteCharacterBandolier(CharacterID(), bds->Number);
}

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;
	LogInventory("Char: [{}] activating set [{}]", GetName(), bss->Number);
	int16 slot = 0;
	int16 WeaponSlot = 0;
	EQ::ItemInstance *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 = bandolierPrimary; BandolierSlot <= bandolierAmmo; BandolierSlot++) {
		// If this bandolier set has an item in this position
		if(m_pp.bandoliers[bss->Number].Items[BandolierSlot].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].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(EQ::invslot::slotCursor)) {
					// Below used to check charges but for some reason
					// m_inv.GetItem(EQ::invslot::slotCursor)->GetCharges()
					// is returning 0.  We know it exists, so removed check.
					// TODO: Why is 0 being returned for slotCursor.
					if (m_inv.GetItem(EQ::invslot::slotCursor)->GetItem()->ID == m_pp.bandoliers[bss->Number].Items[BandolierSlot].ID) {
						slot = EQ::invslot::slotCursor;
					}
					else if (m_inv.GetItem(EQ::invslot::slotCursor)->GetItem()->ItemClass == 1) {
						for(int16 CursorBagSlot = EQ::invbag::CURSOR_BAG_BEGIN; CursorBagSlot <= EQ::invbag::CURSOR_BAG_END; CursorBagSlot++) {
							if (m_inv.GetItem(CursorBagSlot)) {
								if (m_inv.GetItem(CursorBagSlot)->GetItem()->ID == m_pp.bandoliers[bss->Number].Items[BandolierSlot].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) {
					LogInventory("Character does not have required bandolier item for slot [{}]", WeaponSlot);
					EQ::ItemInstance *InvItem = m_inv.PopItem(WeaponSlot);
					if(InvItem) {
						// If there was an item in that weapon slot, put it in the inventory
						LogInventory("returning item [{}] in weapon slot [{}] to inventory",
						InvItem->GetItem()->Name, WeaponSlot);
						LogInventory("returning item [{}] in weapon slot [{}] to inventory", InvItem->GetItem()->Name, WeaponSlot);
						if (MoveItemToInventory(InvItem)) {
							database.SaveInventory(character_id, 0, WeaponSlot);
							LogError("returning item [{}] in weapon slot [{}] to inventory", InvItem->GetItem()->Name, WeaponSlot);
						}
						else {
							LogError("Char: [{}], ERROR returning [{}] 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 = bandolierPrimary; 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].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
				EQ::ItemInstance *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)) {
						LogError("Char: [{}], ERROR returning [{}] 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.
			EQ::ItemInstance *InvItem = m_inv.PopItem(WeaponSlot);
			if(InvItem) {
				LogInventory("Bandolier has no item for slot [{}], returning item [{}] 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 {
					LogError("Char: [{}], ERROR returning [{}] to inventory", GetName(), InvItem->GetItem()->Name);
				}
				safe_delete(InvItem);
			}
		}
	}

	if (RuleI(Character, BandolierSwapDelay) > 0) {
		bandolier_throttle_timer.Start(RuleI(Character, BandolierSwapDelay));
	}

	// finally, recalculate any stat bonuses from the item change
	CalcBonuses();
}

bool Client::MoveItemToInventory(EQ::ItemInstance *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;
	}

	LogInventory("Char: [{}] Returning [{}] 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 = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::slotCursor; i++) { // changed slot max to 30 from 29. client will stack into slot 30 (bags too) before moving.
			if ((((uint64)1 << i) & GetInv().GetLookup()->PossessionsBitmask) == 0)
				continue;

			EQ::ItemInstance* 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->IsClassBag()) {

				int16 BaseSlotID = EQ::InventoryProfile::CalcSlotId(i, EQ::invbag::SLOT_BEGIN);

				uint8 BagSize=InvItem->GetItem()->BagSlots;

				uint8 BagSlot;
				for (BagSlot = EQ::invbag::SLOT_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 = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::slotCursor; i++) { // changed slot max to 30 from 29. client will move into slot 30 (bags too) before pushing onto cursor.
		if ((((uint64)1 << i) & GetInv().GetLookup()->PossessionsBitmask) == 0)
			continue;

		EQ::ItemInstance* 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);

			LogInventory("Char: [{}] Storing in main inventory slot [{}]", GetName(), i);

			return true;
		}
		if (InvItem->IsClassBag() && EQ::InventoryProfile::CanItemFitInContainer(ItemToReturn->GetItem(), InvItem->GetItem())) {

			int16 BaseSlotID = EQ::InventoryProfile::CalcSlotId(i, EQ::invbag::SLOT_BEGIN);

			uint8 BagSize=InvItem->GetItem()->BagSlots;

			for (uint8 BagSlot = EQ::invbag::SLOT_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);

					LogInventory("Char: [{}] Storing in bag slot [{}]", GetName(), BaseSlotID + BagSlot);

					return true;
				}
			}
		}
	}

	// Store on the cursor
	//
	LogInventory("Char: [{}] No space, putting on the cursor", GetName());

	PushItemOnCursor(*ItemToReturn, UpdateClient);

	return true;
}

bool Client::InterrogateInventory(Client* requester, bool log, bool silent, bool allowtrip, bool& error, bool autolog)
{
	if (!requester)
		return false;

	std::map<int16, const EQ::ItemInstance*> instmap;

	// build reference map
	for (int16 index = EQ::invslot::POSSESSIONS_BEGIN; index <= EQ::invslot::POSSESSIONS_END; ++index) {
		auto inst = m_inv[index];
		if (inst == nullptr) { continue; }
		instmap[index] = inst;
	}
	for (int16 index = EQ::invslot::TRIBUTE_BEGIN; index <= EQ::invslot::TRIBUTE_END; ++index) {
		auto inst = m_inv[index];
		if (inst == nullptr) { continue; }
		instmap[index] = inst;
	}
	for (int16 index = EQ::invslot::GUILD_TRIBUTE_BEGIN; index <= EQ::invslot::GUILD_TRIBUTE_END; ++index) {
		auto inst = m_inv[index];
		if (inst == nullptr) { continue; }
		instmap[index] = inst;
	}
	for (int16 index = EQ::invslot::BANK_BEGIN; index <= EQ::invslot::BANK_END; ++index) {
		auto inst = m_inv[index];
		if (inst == nullptr) { continue; }
		instmap[index] = inst;
	}
	for (int16 index = EQ::invslot::SHARED_BANK_BEGIN; index <= EQ::invslot::SHARED_BANK_END; ++index) {
		auto inst = m_inv[index];
		if (inst == nullptr) { continue; }
		instmap[index] = inst;
	}
	for (int16 index = EQ::invslot::TRADE_BEGIN; index <= EQ::invslot::TRADE_END; ++index) {
		auto inst = m_inv[index];
		if (inst == nullptr) { continue; }
		instmap[index] = inst;
	}

	auto tsobject = GetTradeskillObject();
	if (tsobject != nullptr) {
		for (int16 index = EQ::invslot::SLOT_BEGIN; index < EQ::invtype::WORLD_SIZE; ++index) {
			auto inst = tsobject->GetItem(index);
			if (inst == nullptr) { continue; }
			instmap[EQ::invslot::WORLD_BEGIN + index] = inst;
		}
	}

	int limbo = 0;
	for (auto cursor_itr = m_inv.cursor_cbegin(); cursor_itr != m_inv.cursor_cend(); ++cursor_itr, ++limbo) {
		// m_inv.cursor_begin() is referenced as SlotCursor in MapPossessions above
		if (cursor_itr == m_inv.cursor_cbegin())
			continue;

		instmap[EQ::invbag::CURSOR_BAG_BEGIN + limbo] = *cursor_itr;
	}

	// call InterrogateInventory_ for error check
	for (auto instmap_itr = instmap.begin(); (instmap_itr != instmap.end()) && (!error); ++instmap_itr) {
		InterrogateInventory_(true, requester, instmap_itr->first, INVALID_INDEX, instmap_itr->second, nullptr, log, silent, error, 0);
	}

	if (autolog && error && (!log))
		log = true;

	if (log) {
		LogError("Client::InterrogateInventory() called for [{}] by [{}] with an error state of [{}]", GetName(), requester->GetName(), (error ? "TRUE" : "FALSE"));
	}
	if (!silent) {
		requester->Message(Chat::Default, "--- Inventory Interrogation Report for %s (requested by: %s, error state: %s) ---", GetName(), requester->GetName(), (error ? "TRUE" : "FALSE"));
	}

	// call InterrogateInventory_ for report
	for (auto instmap_itr = instmap.begin(); (instmap_itr != instmap.end()); ++instmap_itr) {
		InterrogateInventory_(false, requester, instmap_itr->first, INVALID_INDEX, instmap_itr->second, nullptr, log, silent, error, 0);
	}

	if (error) {
		Message(Chat::Red, "An error has been discovered in your inventory!");
		Message(Chat::Red, "Do not log out, zone or re-arrange items until this");
		Message(Chat::Red, "issue has been resolved or item loss may occur!");

		if (allowtrip)
			TripInterrogateInvState();
	}

	if (log) {
		LogError("Target interrogate inventory flag: [{}]", (GetInterrogateInvState() ? "TRUE" : "FALSE"));
	}
	if (!silent) {
		requester->Message(Chat::Default, "Target interrogation flag: %s", (GetInterrogateInvState() ? "TRUE" : "FALSE"));
		requester->Message(Chat::Default, "--- End of Interrogation Report ---");
	}

	instmap.clear();

	return true;
}

void Client::InterrogateInventory_(bool errorcheck, Client* requester, int16 head, int16 index, const EQ::ItemInstance* inst, const EQ::ItemInstance* parent, bool log, bool silent, bool &error, int depth)
{
	if (depth >= 10) {
		LogDebug("[CLIENT] Client::InterrogateInventory_() - Recursion count has exceeded the maximum allowable (You have a REALLY BIG PROBLEM!!)");
		return;
	}

	if (errorcheck) {
		if (InterrogateInventory_error(head, index, inst, parent, depth)) {
			error = true;
		}
		else {
			if (inst) {
				for (int16 sub = EQ::invbag::SLOT_BEGIN; (sub <= EQ::invbag::SLOT_END) && (!error); ++sub) { // treat any EQ::ItemInstance as having the max internal slots available
					if (inst->GetItem(sub))
						InterrogateInventory_(true, requester, head, sub, inst->GetItem(sub), inst, log, silent, error, depth + 1);
				}
			}
		}
	}
	else {
		bool localerror = InterrogateInventory_error(head, index, inst, parent, depth);
		std::string i;
		std::string p;
		std::string e;

		if (inst) { i = StringFormat("%s (id: %u, cls: %u, aug_t: %u)", inst->GetItem()->Name, inst->GetItem()->ID, inst->GetItem()->ItemClass, inst->GetItem()->AugType); }
		else { i = "NONE"; }
		if (parent) { p = StringFormat("%s (id: %u, cls: %u, aug_t: %u), index: %i", parent->GetItem()->Name, parent->GetItem()->ID, parent->GetItem()->ItemClass, parent->GetItem()->AugType, index); }
		else { p = "NONE"; }
		if (localerror) { e = " [ERROR]"; }
		else { e = ""; }

		if (log) {
			LogError("Head: [{}], Depth: [{}], Instance: [{}], Parent: [{}][{}]",
				head, depth, i.c_str(), p.c_str(), e.c_str());
		}
		if (!silent) {
			requester->Message(Chat::Gray, "%i:%i - inst: %s - parent: %s%s",
				head, depth, i.c_str(), p.c_str(), e.c_str());
		}

		if (inst) {
			for (int16 sub = EQ::invbag::SLOT_BEGIN; (sub <= EQ::invbag::SLOT_END); ++sub) {
				if (inst->GetItem(sub))
					InterrogateInventory_(false, requester, head, sub, inst->GetItem(sub), inst, log, silent, error, depth + 1);
			}
		}
	}

	return;
}

bool Client::InterrogateInventory_error(int16 head, int16 index, const EQ::ItemInstance* inst, const EQ::ItemInstance* parent, int depth)
{
	// very basic error checking - can be elaborated upon if more in-depth testing is needed...

	if (
		EQ::ValueWithin(head, EQ::invslot::EQUIPMENT_BEGIN, EQ::invslot::EQUIPMENT_END) ||
		EQ::ValueWithin(head, EQ::invslot::TRIBUTE_BEGIN, EQ::invslot::TRIBUTE_END) ||
		EQ::ValueWithin(head, EQ::invslot::GUILD_TRIBUTE_BEGIN, EQ::invslot::GUILD_TRIBUTE_END) ||
		EQ::ValueWithin(head, EQ::invslot::WORLD_BEGIN, EQ::invslot::WORLD_END) ||
		EQ::ValueWithin(head, EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END)
	) {
		switch (depth)
		{
		case 0: // requirement: inst is extant
			if (!inst)
				return true;
			break;
		case 1: // requirement: parent is common and inst is augment
			if ((!parent) || (!inst))
				return true;
			if (!parent->IsType(EQ::item::ItemClassCommon))
				return true;
			if (index > EQ::invaug::SOCKET_END)
				return true;
			break;
		default: // requirement: none (something bad happened...)
			return true;
		}
	}
	else if (
		(head >= EQ::invslot::GENERAL_BEGIN && head <= EQ::invslot::GENERAL_END) ||
		(head == EQ::invslot::slotCursor) ||
		(head >= EQ::invslot::BANK_BEGIN && head <= EQ::invslot::BANK_END) ||
		(head >= EQ::invslot::SHARED_BANK_BEGIN && head <= EQ::invslot::SHARED_BANK_END) ||
		(head >= EQ::invslot::TRADE_BEGIN && head <= EQ::invslot::TRADE_END)) {
		switch (depth)
		{
		case 0: // requirement: inst is extant
			if (!inst)
				return true;
			break;
		case 1: // requirement: parent is common and inst is augment ..or.. parent is container and inst is extant
			if ((!parent) || (!inst))
				return true;
			if (parent->IsType(EQ::item::ItemClassBag))
				break;
			if (parent->IsClassBook())
				return true;
			if (parent->IsClassCommon()) {
				if (!(inst->GetItem()->AugType > 0))
					return true;
				if (index > EQ::invaug::SOCKET_END)
					return true;
			}
			break;
		case 2: // requirement: parent is common and inst is augment
			if ((!parent) || (!inst))
				return true;
			if (parent->IsType(EQ::item::ItemClassBag))
				return true;
			if (parent->IsClassBook())
				return true;
			if (parent->IsClassCommon()) {
				if (!(inst->GetItem()->AugType > 0))
					return true;
				if (index > EQ::invaug::SOCKET_END)
					return true;
			}
			break;
		default: // requirement: none (something bad happened again...)
			return true;
		}
	}
	else {
		return true;
	}

	return false;
}

void EQ::InventoryProfile::SetCustomItemData(uint32 character_id, int16 slot_id, const std::string &identifier, const std::string &value) {
	EQ::ItemInstance *inst = GetItem(slot_id);
	if(inst) {
		inst->SetCustomData(identifier, value);
		database.SaveInventory(character_id, inst, slot_id);
	}
}

void EQ::InventoryProfile::SetCustomItemData(uint32 character_id, int16 slot_id, const std::string &identifier, int value) {
	EQ::ItemInstance *inst = GetItem(slot_id);
	if(inst) {
		inst->SetCustomData(identifier, value);
		database.SaveInventory(character_id, inst, slot_id);
	}
}

void EQ::InventoryProfile::SetCustomItemData(uint32 character_id, int16 slot_id, const std::string &identifier, float value) {
	EQ::ItemInstance *inst = GetItem(slot_id);
	if(inst) {
		inst->SetCustomData(identifier, value);
		database.SaveInventory(character_id, inst, slot_id);
	}
}

void EQ::InventoryProfile::SetCustomItemData(uint32 character_id, int16 slot_id, const std::string &identifier, bool value) {
	EQ::ItemInstance *inst = GetItem(slot_id);
	if(inst) {
		inst->SetCustomData(identifier, value);
		database.SaveInventory(character_id, inst, slot_id);
	}
}

std::string EQ::InventoryProfile::GetCustomItemData(int16 slot_id, const std::string &identifier) {
	EQ::ItemInstance *inst = GetItem(slot_id);
	if(inst) {
		return inst->GetCustomData(identifier);
	}
	return "";
}

const int EQ::InventoryProfile::GetItemStatValue(uint32 item_id, const std::string &identifier) {
	if (identifier.empty()) {
		return 0;
	}

	const auto* inst = database.CreateItem(item_id);
	if (!inst) {
		return 0;
	}

	const auto* item = inst->GetItem();
	if (!item) {
		return 0;
	}

	int stat = 0;

	if (Strings::EqualFold(identifier, "itemclass")) {
		stat = static_cast<int>(item->ItemClass);
	}

	if (Strings::EqualFold(identifier, "id")) {
		stat = static_cast<int>(item->ID);
	}

	if (Strings::EqualFold(identifier, "idfile")) {
		stat = Strings::IsNumber(&item->IDFile[2]) ? Strings::ToInt(&item->IDFile[2]) : 0;
	}

	if (Strings::EqualFold(identifier, "weight")) {
		stat = static_cast<int>(item->Weight);
	}

	if (Strings::EqualFold(identifier, "norent")) {
		stat = static_cast<int>(item->NoRent);
	}

	if (Strings::EqualFold(identifier, "nodrop")) {
		stat = static_cast<int>(item->NoDrop);
	}

	if (Strings::EqualFold(identifier, "size")) {
		stat = static_cast<int>(item->Size);
	}

	if (Strings::EqualFold(identifier, "slots")) {
		stat = static_cast<int>(item->Slots);
	}

	if (Strings::EqualFold(identifier, "price")) {
		stat = static_cast<int>(item->Price);
	}

	if (Strings::EqualFold(identifier, "icon")) {
		stat = static_cast<int>(item->Icon);
	}

	if (Strings::EqualFold(identifier, "loregroup")) {
		stat = static_cast<int>(item->LoreGroup);
	}

	if (Strings::EqualFold(identifier, "loreflag")) {
		stat = static_cast<int>(item->LoreFlag);
	}

	if (Strings::EqualFold(identifier, "pendingloreflag")) {
		stat = static_cast<int>(item->PendingLoreFlag);
	}

	if (Strings::EqualFold(identifier, "artifactflag")) {
		stat = static_cast<int>(item->ArtifactFlag);
	}

	if (Strings::EqualFold(identifier, "summonedflag")) {
		stat = static_cast<int>(item->SummonedFlag);
	}

	if (Strings::EqualFold(identifier, "fvnodrop")) {
		stat = static_cast<int>(item->FVNoDrop);
	}

	if (Strings::EqualFold(identifier, "favor")) {
		stat = static_cast<int>(item->Favor);
	}

	if (Strings::EqualFold(identifier, "guildfavor")) {
		stat = static_cast<int>(item->GuildFavor);
	}

	if (Strings::EqualFold(identifier, "pointtype")) {
		stat = static_cast<int>(item->PointType);
	}

	if (Strings::EqualFold(identifier, "bagtype")) {
		stat = static_cast<int>(item->BagType);
	}

	if (Strings::EqualFold(identifier, "bagslots")) {
		stat = static_cast<int>(item->BagSlots);
	}

	if (Strings::EqualFold(identifier, "bagsize")) {
		stat = static_cast<int>(item->BagSize);
	}

	if (Strings::EqualFold(identifier, "bagwr")) {
		stat = static_cast<int>(item->BagWR);
	}

	if (Strings::EqualFold(identifier, "benefitflag")) {
		stat = static_cast<int>(item->BenefitFlag);
	}

	if (Strings::EqualFold(identifier, "tradeskills")) {
		stat = static_cast<int>(item->Tradeskills);
	}

	if (Strings::EqualFold(identifier, "cr")) {
		stat = static_cast<int>(item->CR);
	}

	if (Strings::EqualFold(identifier, "dr")) {
		stat = static_cast<int>(item->DR);
	}

	if (Strings::EqualFold(identifier, "pr")) {
		stat = static_cast<int>(item->PR);
	}

	if (Strings::EqualFold(identifier, "mr")) {
		stat = static_cast<int>(item->MR);
	}

	if (Strings::EqualFold(identifier, "fr")) {
		stat = static_cast<int>(item->FR);
	}

	if (Strings::EqualFold(identifier, "astr")) {
		stat = static_cast<int>(item->AStr);
	}

	if (Strings::EqualFold(identifier, "asta")) {
		stat = static_cast<int>(item->ASta);
	}

	if (Strings::EqualFold(identifier, "aagi")) {
		stat = static_cast<int>(item->AAgi);
	}

	if (Strings::EqualFold(identifier, "adex")) {
		stat = static_cast<int>(item->ADex);
	}

	if (Strings::EqualFold(identifier, "acha")) {
		stat = static_cast<int>(item->ACha);
	}

	if (Strings::EqualFold(identifier, "aint")) {
		stat = static_cast<int>(item->AInt);
	}

	if (Strings::EqualFold(identifier, "awis")) {
		stat = static_cast<int>(item->AWis);
	}

	if (Strings::EqualFold(identifier, "hp")) {
		stat = static_cast<int>(item->HP);
	}

	if (Strings::EqualFold(identifier, "mana")) {
		stat = static_cast<int>(item->Mana);
	}

	if (Strings::EqualFold(identifier, "ac")) {
		stat = static_cast<int>(item->AC);
	}

	if (Strings::EqualFold(identifier, "deity")) {
		stat = static_cast<int>(item->Deity);
	}

	if (Strings::EqualFold(identifier, "skillmodvalue")) {
		stat = static_cast<int>(item->SkillModValue);
	}

	if (Strings::EqualFold(identifier, "skillmodtype")) {
		stat = static_cast<int>(item->SkillModType);
	}

	if (Strings::EqualFold(identifier, "banedmgrace")) {
		stat = static_cast<int>(item->BaneDmgRace);
	}

	if (Strings::EqualFold(identifier, "banedmgamt")) {
		stat = static_cast<int>(item->BaneDmgAmt);
	}

	if (Strings::EqualFold(identifier, "banedmgbody")) {
		stat = static_cast<int>(item->BaneDmgBody);
	}

	if (Strings::EqualFold(identifier, "magic")) {
		stat = static_cast<int>(item->Magic);
	}

	if (Strings::EqualFold(identifier, "casttime_")) {
		stat = static_cast<int>(item->CastTime_);
	}

	if (Strings::EqualFold(identifier, "reqlevel")) {
		stat = static_cast<int>(item->ReqLevel);
	}

	if (Strings::EqualFold(identifier, "bardtype")) {
		stat = static_cast<int>(item->BardType);
	}

	if (Strings::EqualFold(identifier, "bardvalue")) {
		stat = static_cast<int>(item->BardValue);
	}

	if (Strings::EqualFold(identifier, "light")) {
		stat = static_cast<int>(item->Light);
	}

	if (Strings::EqualFold(identifier, "delay")) {
		stat = static_cast<int>(item->Delay);
	}

	if (Strings::EqualFold(identifier, "reclevel")) {
		stat = static_cast<int>(item->RecLevel);
	}

	if (Strings::EqualFold(identifier, "recskill")) {
		stat = static_cast<int>(item->RecSkill);
	}

	if (Strings::EqualFold(identifier, "elemdmgtype")) {
		stat = static_cast<int>(item->ElemDmgType);
	}

	if (Strings::EqualFold(identifier, "elemdmgamt")) {
		stat = static_cast<int>(item->ElemDmgAmt);
	}

	if (Strings::EqualFold(identifier, "range")) {
		stat = static_cast<int>(item->Range);
	}

	if (Strings::EqualFold(identifier, "damage")) {
		stat = static_cast<int>(item->Damage);
	}

	if (Strings::EqualFold(identifier, "color")) {
		stat = static_cast<int>(item->Color);
	}

	if (Strings::EqualFold(identifier, "classes")) {
		stat = static_cast<int>(item->Classes);
	}

	if (Strings::EqualFold(identifier, "races")) {
		stat = static_cast<int>(item->Races);
	}

	if (Strings::EqualFold(identifier, "maxcharges")) {
		stat = static_cast<int>(item->MaxCharges);
	}

	if (Strings::EqualFold(identifier, "itemtype")) {
		stat = static_cast<int>(item->ItemType);
	}

	if (Strings::EqualFold(identifier, "material")) {
		stat = static_cast<int>(item->Material);
	}

	if (Strings::EqualFold(identifier, "casttime")) {
		stat = static_cast<int>(item->CastTime);
	}

	if (Strings::EqualFold(identifier, "elitematerial")) {
		stat = static_cast<int>(item->EliteMaterial);
	}

	if (Strings::EqualFold(identifier, "herosforgemodel")) {
		stat = static_cast<int>(item->HerosForgeModel);
	}

	if (Strings::EqualFold(identifier, "procrate")) {
		stat = static_cast<int>(item->ProcRate);
	}

	if (Strings::EqualFold(identifier, "combateffects")) {
		stat = static_cast<int>(item->CombatEffects);
	}

	if (Strings::EqualFold(identifier, "shielding")) {
		stat = static_cast<int>(item->Shielding);
	}

	if (Strings::EqualFold(identifier, "stunresist")) {
		stat = static_cast<int>(item->StunResist);
	}

	if (Strings::EqualFold(identifier, "strikethrough")) {
		stat = static_cast<int>(item->StrikeThrough);
	}

	if (Strings::EqualFold(identifier, "extradmgskill")) {
		stat = static_cast<int>(item->ExtraDmgSkill);
	}

	if (Strings::EqualFold(identifier, "extradmgamt")) {
		stat = static_cast<int>(item->ExtraDmgAmt);
	}

	if (Strings::EqualFold(identifier, "spellshield")) {
		stat = static_cast<int>(item->SpellShield);
	}

	if (Strings::EqualFold(identifier, "avoidance")) {
		stat = static_cast<int>(item->Avoidance);
	}

	if (Strings::EqualFold(identifier, "accuracy")) {
		stat = static_cast<int>(item->Accuracy);
	}

	if (Strings::EqualFold(identifier, "charmfileid")) {
		stat = static_cast<int>(item->CharmFileID);
	}

	if (Strings::EqualFold(identifier, "factionmod1")) {
		stat = static_cast<int>(item->FactionMod1);
	}

	if (Strings::EqualFold(identifier, "factionmod2")) {
		stat = static_cast<int>(item->FactionMod2);
	}

	if (Strings::EqualFold(identifier, "factionmod3")) {
		stat = static_cast<int>(item->FactionMod3);
	}

	if (Strings::EqualFold(identifier, "factionmod4")) {
		stat = static_cast<int>(item->FactionMod4);
	}

	if (Strings::EqualFold(identifier, "factionamt1")) {
		stat = static_cast<int>(item->FactionAmt1);
	}

	if (Strings::EqualFold(identifier, "factionamt2")) {
		stat = static_cast<int>(item->FactionAmt2);
	}

	if (Strings::EqualFold(identifier, "factionamt3")) {
		stat = static_cast<int>(item->FactionAmt3);
	}

	if (Strings::EqualFold(identifier, "factionamt4")) {
		stat = static_cast<int>(item->FactionAmt4);
	}

	if (Strings::EqualFold(identifier, "augtype")) {
		stat = static_cast<int>(item->AugType);
	}

	if (Strings::EqualFold(identifier, "ldontheme")) {
		stat = static_cast<int>(item->LDoNTheme);
	}

	if (Strings::EqualFold(identifier, "ldonprice")) {
		stat = static_cast<int>(item->LDoNPrice);
	}

	if (Strings::EqualFold(identifier, "ldonsold")) {
		stat = static_cast<int>(item->LDoNSold);
	}

	if (Strings::EqualFold(identifier, "banedmgraceamt")) {
		stat = static_cast<int>(item->BaneDmgRaceAmt);
	}

	if (Strings::EqualFold(identifier, "augrestrict")) {
		stat = static_cast<int>(item->AugRestrict);
	}

	if (Strings::EqualFold(identifier, "endur")) {
		stat = static_cast<int>(item->Endur);
	}

	if (Strings::EqualFold(identifier, "dotshielding")) {
		stat = static_cast<int>(item->DotShielding);
	}

	if (Strings::EqualFold(identifier, "attack")) {
		stat = static_cast<int>(item->Attack);
	}

	if (Strings::EqualFold(identifier, "regen")) {
		stat = static_cast<int>(item->Regen);
	}

	if (Strings::EqualFold(identifier, "manaregen")) {
		stat = static_cast<int>(item->ManaRegen);
	}

	if (Strings::EqualFold(identifier, "enduranceregen")) {
		stat = static_cast<int>(item->EnduranceRegen);
	}

	if (Strings::EqualFold(identifier, "haste")) {
		stat = static_cast<int>(item->Haste);
	}

	if (Strings::EqualFold(identifier, "damageshield")) {
		stat = static_cast<int>(item->DamageShield);
	}

	if (Strings::EqualFold(identifier, "recastdelay")) {
		stat = static_cast<int>(item->RecastDelay);
	}

	if (Strings::EqualFold(identifier, "recasttype")) {
		stat = static_cast<int>(item->RecastType);
	}

	if (Strings::EqualFold(identifier, "augdistiller")) {
		stat = static_cast<int>(item->AugDistiller);
	}

	if (Strings::EqualFold(identifier, "attuneable")) {
		stat = static_cast<int>(item->Attuneable);
	}

	if (Strings::EqualFold(identifier, "nopet")) {
		stat = static_cast<int>(item->NoPet);
	}

	if (Strings::EqualFold(identifier, "potionbelt")) {
		stat = static_cast<int>(item->PotionBelt);
	}

	if (Strings::EqualFold(identifier, "stackable")) {
		stat = static_cast<int>(item->Stackable);
	}

	if (Strings::EqualFold(identifier, "notransfer")) {
		stat = static_cast<int>(item->NoTransfer);
	}

	if (Strings::EqualFold(identifier, "questitemflag")) {
		stat = static_cast<int>(item->QuestItemFlag);
	}

	if (Strings::EqualFold(identifier, "stacksize")) {
		stat = static_cast<int>(item->StackSize);
	}

	if (Strings::EqualFold(identifier, "potionbeltslots")) {
		stat = static_cast<int>(item->PotionBeltSlots);
	}

	if (Strings::EqualFold(identifier, "book")) {
		stat = static_cast<int>(item->Book);
	}

	if (Strings::EqualFold(identifier, "booktype")) {
		stat = static_cast<int>(item->BookType);
	}

	if (Strings::EqualFold(identifier, "svcorruption")) {
		stat = static_cast<int>(item->SVCorruption);
	}

	if (Strings::EqualFold(identifier, "purity")) {
		stat = static_cast<int>(item->Purity);
	}

	if (Strings::EqualFold(identifier, "backstabdmg")) {
		stat = static_cast<int>(item->BackstabDmg);
	}

	if (Strings::EqualFold(identifier, "dsmitigation")) {
		stat = static_cast<int>(item->DSMitigation);
	}

	if (Strings::EqualFold(identifier, "heroicstr")) {
		stat = static_cast<int>(item->HeroicStr);
	}

	if (Strings::EqualFold(identifier, "heroicint")) {
		stat = static_cast<int>(item->HeroicInt);
	}

	if (Strings::EqualFold(identifier, "heroicwis")) {
		stat = static_cast<int>(item->HeroicWis);
	}

	if (Strings::EqualFold(identifier, "heroicagi")) {
		stat = static_cast<int>(item->HeroicAgi);
	}

	if (Strings::EqualFold(identifier, "heroicdex")) {
		stat = static_cast<int>(item->HeroicDex);
	}

	if (Strings::EqualFold(identifier, "heroicsta")) {
		stat = static_cast<int>(item->HeroicSta);
	}

	if (Strings::EqualFold(identifier, "heroiccha")) {
		stat = static_cast<int>(item->HeroicCha);
	}

	if (Strings::EqualFold(identifier, "heroicmr")) {
		stat = static_cast<int>(item->HeroicMR);
	}

	if (Strings::EqualFold(identifier, "heroicfr")) {
		stat = static_cast<int>(item->HeroicFR);
	}

	if (Strings::EqualFold(identifier, "heroiccr")) {
		stat = static_cast<int>(item->HeroicCR);
	}

	if (Strings::EqualFold(identifier, "heroicdr")) {
		stat = static_cast<int>(item->HeroicDR);
	}

	if (Strings::EqualFold(identifier, "heroicpr")) {
		stat = static_cast<int>(item->HeroicPR);
	}

	if (Strings::EqualFold(identifier, "heroicsvcorrup")) {
		stat = static_cast<int>(item->HeroicSVCorrup);
	}

	if (Strings::EqualFold(identifier, "healamt")) {
		stat = static_cast<int>(item->HealAmt);
	}

	if (Strings::EqualFold(identifier, "spelldmg")) {
		stat = static_cast<int>(item->SpellDmg);
	}

	if (Strings::EqualFold(identifier, "ldonsellbackrate")) {
		stat = static_cast<int>(item->LDoNSellBackRate);
	}

	if (Strings::EqualFold(identifier, "scriptfileid")) {
		stat = static_cast<int>(item->ScriptFileID);
	}

	if (Strings::EqualFold(identifier, "expendablearrow")) {
		stat = static_cast<int>(item->ExpendableArrow);
	}

	if (Strings::EqualFold(identifier, "clairvoyance")) {
		stat = static_cast<int>(item->Clairvoyance);
	}

	if (Strings::EqualFold(identifier, "clickeffect")) {
		stat = static_cast<int>(item->Click.Effect);
	}

	if (Strings::EqualFold(identifier, "clicktype")) {
		stat = static_cast<int>(item->Click.Type);
	}

	if (Strings::EqualFold(identifier, "clicklevel")) {
		stat = static_cast<int>(item->Click.Level);
	}

	if (Strings::EqualFold(identifier, "clicklevel2")) {
		stat = static_cast<int>(item->Click.Level2);
	}

	if (Strings::EqualFold(identifier, "proceffect")) {
		stat = static_cast<int>(item->Proc.Effect);
	}

	if (Strings::EqualFold(identifier, "proctype")) {
		stat = static_cast<int>(item->Proc.Type);
	}

	if (Strings::EqualFold(identifier, "proclevel")) {
		stat = static_cast<int>(item->Proc.Level);
	}

	if (Strings::EqualFold(identifier, "proclevel2")) {
		stat = static_cast<int>(item->Proc.Level2);
	}

	if (Strings::EqualFold(identifier, "worneffect")) {
		stat = static_cast<int>(item->Worn.Effect);
	}

	if (Strings::EqualFold(identifier, "worntype")) {
		stat = static_cast<int>(item->Worn.Type);
	}

	if (Strings::EqualFold(identifier, "wornlevel")) {
		stat = static_cast<int>(item->Worn.Level);
	}

	if (Strings::EqualFold(identifier, "wornlevel2")) {
		stat = static_cast<int>(item->Worn.Level2);
	}

	if (Strings::EqualFold(identifier, "focuseffect")) {
		stat = static_cast<int>(item->Focus.Effect);
	}

	if (Strings::EqualFold(identifier, "focustype")) {
		stat = static_cast<int>(item->Focus.Type);
	}

	if (Strings::EqualFold(identifier, "focuslevel")) {
		stat = static_cast<int>(item->Focus.Level);
	}

	if (Strings::EqualFold(identifier, "focuslevel2")) {
		stat = static_cast<int>(item->Focus.Level2);
	}

	if (Strings::EqualFold(identifier, "scrolleffect")) {
		stat = static_cast<int>(item->Scroll.Effect);
	}

	if (Strings::EqualFold(identifier, "scrolltype")) {
		stat = static_cast<int>(item->Scroll.Type);
	}

	if (Strings::EqualFold(identifier, "scrolllevel")) {
		stat = static_cast<int>(item->Scroll.Level);
	}

	if (Strings::EqualFold(identifier, "scrolllevel2")) {
		stat = static_cast<int>(item->Scroll.Level2);
	}

	if (Strings::EqualFold(identifier, "augslot1type")) {
		stat = static_cast<int>(item->AugSlotType[0]);
	}

	if (Strings::EqualFold(identifier, "augslot2type")) {
		stat = static_cast<int>(item->AugSlotType[1]);
	}

	if (Strings::EqualFold(identifier, "augslot3type")) {
		stat = static_cast<int>(item->AugSlotType[2]);
	}

	if (Strings::EqualFold(identifier, "augslot4type")) {
		stat = static_cast<int>(item->AugSlotType[3]);
	}

	if (Strings::EqualFold(identifier, "augslot5type")) {
		stat = static_cast<int>(item->AugSlotType[4]);
	}

	if (Strings::EqualFold(identifier, "augslot6type")) {
		stat = static_cast<int>(item->AugSlotType[5]);
	}

	if (Strings::EqualFold(identifier, "augslot1visible")) {
		stat = static_cast<int>(item->AugSlotVisible[0]);
	}

	if (Strings::EqualFold(identifier, "augslot2visible")) {
		stat = static_cast<int>(item->AugSlotVisible[1]);
	}

	if (Strings::EqualFold(identifier, "augslot3visible")) {
		stat = static_cast<int>(item->AugSlotVisible[2]);
	}

	if (Strings::EqualFold(identifier, "augslot4visible")) {
		stat = static_cast<int>(item->AugSlotVisible[3]);
	}

	if (Strings::EqualFold(identifier, "augslot5visible")) {
		stat = static_cast<int>(item->AugSlotVisible[4]);
	}

	if (Strings::EqualFold(identifier, "augslot6visible")) {
		stat = static_cast<int>(item->AugSlotVisible[5]);
	}

	safe_delete(inst);
	return stat;
}

// Returns a slot's item ID (returns INVALID_ID if not found)
int32 Bot::GetItemIDAt(int16 slot_id) {
	if (slot_id <= EQ::invslot::POSSESSIONS_END && slot_id >= EQ::invslot::POSSESSIONS_BEGIN) {
		if ((((uint64)1 << slot_id) & GetInv().GetLookup()->PossessionsBitmask) == 0) {
			return INVALID_ID;
		}
	}
	else if (slot_id <= EQ::invbag::GENERAL_BAGS_END && slot_id >= EQ::invbag::GENERAL_BAGS_BEGIN) {
		auto temp_slot = EQ::invslot::GENERAL_BEGIN + ((slot_id - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT);
		if ((((uint64)1 << temp_slot) & GetInv().GetLookup()->PossessionsBitmask) == 0) {
			return INVALID_ID;
		}
	}
	else if (slot_id <= EQ::invslot::BANK_END && slot_id >= EQ::invslot::BANK_BEGIN) {
		if ((slot_id - EQ::invslot::BANK_BEGIN) >= GetInv().GetLookup()->InventoryTypeSize.Bank) {
			return INVALID_ID;
		}
	}
	else if (slot_id <= EQ::invbag::BANK_BAGS_END && slot_id >= EQ::invbag::BANK_BAGS_BEGIN) {
		auto temp_slot = (slot_id - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT;
		if (temp_slot >= GetInv().GetLookup()->InventoryTypeSize.Bank) {
			return INVALID_ID;
		}
	}

	const EQ::ItemInstance* 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-5)
int32 Bot::GetAugmentIDAt(int16 slot_id, uint8 augslot) {
	if (slot_id <= EQ::invslot::POSSESSIONS_END && slot_id >= EQ::invslot::POSSESSIONS_BEGIN) {
		if ((((uint64)1 << slot_id) & GetInv().GetLookup()->PossessionsBitmask) == 0) {
			return INVALID_ID;
		}
	}
	else if (slot_id <= EQ::invbag::GENERAL_BAGS_END && slot_id >= EQ::invbag::GENERAL_BAGS_BEGIN) {
		auto temp_slot = EQ::invslot::GENERAL_BEGIN + ((slot_id - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT);
		if ((((uint64)1 << temp_slot) & GetInv().GetLookup()->PossessionsBitmask) == 0) {
			return INVALID_ID;
		}
	}
	else if (slot_id <= EQ::invslot::BANK_END && slot_id >= EQ::invslot::BANK_BEGIN) {
		if ((slot_id - EQ::invslot::BANK_BEGIN) >= GetInv().GetLookup()->InventoryTypeSize.Bank) {
			return INVALID_ID;
		}
	}
	else if (slot_id <= EQ::invbag::BANK_BAGS_END && slot_id >= EQ::invbag::BANK_BAGS_BEGIN) {
		auto temp_slot = (slot_id - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT;
		if (temp_slot >= GetInv().GetLookup()->InventoryTypeSize.Bank) {
			return INVALID_ID;
		}
	}

	const EQ::ItemInstance* inst = m_inv[slot_id];
	if (inst && inst->GetAugmentItemID(augslot)) {
		return inst->GetAugmentItemID(augslot);
	}

	// None found
	return INVALID_ID;
}

bool Client::IsAugmentRestricted(uint8 item_type, uint32 augment_restriction)
{
	switch (augment_restriction) {
		case EQ::item::AugRestrictionAny:
			break;
		case EQ::item::AugRestrictionArmor:
			switch (item_type) {
				case EQ::item::ItemTypeArmor:
					break;
				default:
					return true;
			}
			break;
		case EQ::item::AugRestrictionWeapons:
			switch (item_type) {
				case EQ::item::ItemType1HSlash:
				case EQ::item::ItemType1HBlunt:
				case EQ::item::ItemType1HPiercing:
				case EQ::item::ItemTypeMartial:
				case EQ::item::ItemType2HSlash:
				case EQ::item::ItemType2HBlunt:
				case EQ::item::ItemType2HPiercing:
				case EQ::item::ItemTypeBow:
					break;
				default:
					return true;
			}
			break;
		case EQ::item::AugRestriction1HWeapons:
			switch (item_type) {
				case EQ::item::ItemType1HSlash:
				case EQ::item::ItemType1HBlunt:
				case EQ::item::ItemType1HPiercing:
				case EQ::item::ItemTypeMartial:
					break;
				default:
					return true;
			}
			break;
		case EQ::item::AugRestriction2HWeapons:
			switch (item_type) {
				case EQ::item::ItemType2HSlash:
				case EQ::item::ItemType2HBlunt:
				case EQ::item::ItemType2HPiercing:
				case EQ::item::ItemTypeBow:
					break;
				default:
					return true;
			}
			break;
		case EQ::item::AugRestriction1HSlash:
			switch (item_type) {
				case EQ::item::ItemType1HSlash:
					break;
				default:
					return true;
			}
			break;
		case EQ::item::AugRestriction1HBlunt:
			switch (item_type) {
				case EQ::item::ItemType1HBlunt:
					break;
				default:
					return true;
			}
			break;
		case EQ::item::AugRestrictionPiercing:
			switch (item_type) {
				case EQ::item::ItemType1HPiercing:
					break;
				default:
					return true;
			}
			break;
		case EQ::item::AugRestrictionHandToHand:
			switch (item_type) {
				case EQ::item::ItemTypeMartial:
					break;
				default:
					return true;
			}
			break;
		case EQ::item::AugRestriction2HSlash:
			switch (item_type) {
				case EQ::item::ItemType2HSlash:
					break;
				default:
					return true;
			}
			break;
		case EQ::item::AugRestriction2HBlunt:
			switch (item_type) {
				case EQ::item::ItemType2HBlunt:
					break;
				default:
					return true;
			}
			break;
		case EQ::item::AugRestriction2HPierce:
			switch (item_type) {
				case EQ::item::ItemType2HPiercing:
					break;
				default:
					return true;
			}
			break;
		case EQ::item::AugRestrictionBows:
			switch (item_type) {
				case EQ::item::ItemTypeBow:
					break;
				default:
					return true;
			}
			break;
		case EQ::item::AugRestrictionShields:
			switch (item_type) {
				case EQ::item::ItemTypeShield:
					break;
				default:
					return true;
			}
			break;
		case EQ::item::AugRestriction1HSlash1HBluntOrHandToHand:
			switch (item_type) {
				case EQ::item::ItemType1HSlash:
				case EQ::item::ItemType1HBlunt:
				case EQ::item::ItemTypeMartial:
					break;
				default:
					return true;
			}
			break;
		case EQ::item::AugRestriction1HBluntOrHandToHand:
			switch (item_type) {
				case EQ::item::ItemType1HBlunt:
				case EQ::item::ItemTypeMartial:
					break;
				default:
					return true;
			}
			break;
		case EQ::item::AugRestrictionUnknown1:
		case EQ::item::AugRestrictionUnknown2:
		case EQ::item::AugRestrictionUnknown3:
		default:
			return true;
	}

	return false;
}

void Client::SummonItemIntoInventory(
	uint32 item_id,
	int16 charges,
	uint32 aug1,
	uint32 aug2,
	uint32 aug3,
	uint32 aug4,
	uint32 aug5,
	uint32 aug6,
	bool is_attuned
)
{
	auto *inst = database.CreateItem(
		item_id,
		charges,
		aug1,
		aug2,
		aug3,
		aug4,
		aug5,
		aug6,
		is_attuned
	);

	if (!inst) {
		return;
	}

	// Try stacking first if the item is stackable, then fall back to finding a free slot
	if (!PutItemInInventoryWithStacking(inst)) {
		// PutItemInInventoryWithStacking failed, fall back to original behavior
		const bool  is_arrow = inst->GetItem()->ItemType == EQ::item::ItemTypeArrow;
		const int16 slot_id  = m_inv.FindFreeSlot(
			inst->IsClassBag(),
			true,
			inst->GetItem()->Size,
			is_arrow
		);

		SummonItem(
			item_id,
			charges,
			aug1,
			aug2,
			aug3,
			aug4,
			aug5,
			aug6,
			is_attuned,
			slot_id
		);
	}

	safe_delete(inst);
}

bool Client::HasItemOnCorpse(uint32 item_id)
{
	auto corpses = CharacterCorpsesRepository::GetWhere(database, fmt::format("charid = {}", CharacterID()));
	if (corpses.empty()) {
		return false;
	}

	std::vector<uint32> corpse_ids;
	corpse_ids.reserve(corpses.size());

	for (auto &corpse : corpses) {
		corpse_ids.push_back(corpse.id);
	}

	auto items = CharacterCorpseItemsRepository::GetWhere(
		database,
		fmt::format(
			"corpse_id IN ({})",
			Strings::Join(corpse_ids, ",")
		)
	);

	for (auto &item : items) {
		if (item.item_id == item_id) {
			return true;
		}
		if (item.aug_1 == item_id || item.aug_2 == item_id ||
			item.aug_3 == item_id || item.aug_4 == item_id ||
			item.aug_5 == item_id || item.aug_6 == item_id) {
			return true;
		}
	}

	return false;
}

bool Client::PutItemInInventoryWithStacking(EQ::ItemInstance *inst)
{
	auto free_id = GetInv().FindFirstFreeSlotThatFitsItem(inst->GetItem());
	if (inst->IsStackable()) {
		if (TryStacking(inst, ItemPacketTrade, true, false)) {
			return true;
		}
	}
	if (free_id != INVALID_INDEX) {
		if (PutItemInInventory(free_id, *inst, true)) {
			return true;
		}
	}
	return false;
};

bool Client::FindNumberOfFreeInventorySlotsWithSizeCheck(std::vector<BuyerLineTradeItems_Struct> items)
{
	uint32 count = 0;
	for (int16         i = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++) {
		if ((((uint64) 1 << i) & GetInv().GetLookup()->PossessionsBitmask) == 0) {
			continue;
		}

		EQ::ItemInstance *inv_item = GetInv().GetItem(i);

		if (!inv_item) {
			// Found available slot in personal inventory.  Fits all sizes
			count++;
		}

		if (count >= items.size()) {
			return true;
		}

		if (inv_item->IsClassBag()) {
			for (auto const& item:items) {
				auto item_tmp = database.GetItem(item.item_id);
				if (EQ::InventoryProfile::CanItemFitInContainer(item_tmp, inv_item->GetItem())) {
					int16 base_slot_id = EQ::InventoryProfile::CalcSlotId(i, EQ::invbag::SLOT_BEGIN);
					uint8 bag_size     = inv_item->GetItem()->BagSlots;

					for (uint8 bag_slot = EQ::invbag::SLOT_BEGIN; bag_slot < bag_size; bag_slot++) {
						auto bag_item = GetInv().GetItem(base_slot_id + bag_slot);
						if (!bag_item) {
							// Found a bag slot that fits the item
							count++;
						}
					}

					if (count >= items.size()) {
						return true;
					}
				}
			}
		}
	}
	return false;
};
