View Single Post
  #8  
Old 11-02-2012, 06:26 PM
Uleat's Avatar
Uleat
Developer
 
Join Date: Apr 2012
Location: North Carolina
Posts: 2,815
Default

THIS IS A BETA PATCH. CLIENT TESTING IS STILL NEEDED

The previous post is referenced in the patch to eliminate loading up the code.


For now, this is the best I can do. Players, 'use #resyncinv help' for a quick description of what to expect.


It probably still needs minor timing tweaking, but here it is:

[ResyncInv.patch] (9 files, 627 lines)
Code:
Index: common/Item.cpp
===================================================================
--- common/Item.cpp	(revision 2244)
+++ common/Item.cpp	(working copy)
@@ -668,22 +668,20 @@
 }
 
 // Swap items in inventory
-void Inventory::SwapItem(sint16 slot_a, sint16 slot_b)
-{
-	// Temp holding area for a
+bool Inventory::SwapItem(sint16 slot_a, sint16 slot_b)
+{ // Modified to fail all item swaps into illegal slots..'return false' will trigger resync -U
+
+	// Temp holding areas for a & b
 	ItemInst* inst_a = GetItem(slot_a);
+	ItemInst* inst_b = GetItem(slot_b);
 
-	if(inst_a)
-	{
-		if(!inst_a->IsSlotAllowed(slot_b))
-			return;
-	}
+	if(inst_a) { if(!inst_a->IsSlotAllowed(slot_b)) { return false; } }
+	if(inst_b) { if(!inst_b->IsSlotAllowed(slot_a)) { return false; } }
 
-	// Copy b->a
-	_PutItem(slot_a, GetItem(slot_b));
-	
-	// Copy a->b
-	_PutItem(slot_b, inst_a);
+	_PutItem(slot_a, inst_b); // Copy b->a
+	_PutItem(slot_b, inst_a); // Copy a->b
+
+	return true;
 }
 
 // Checks that user has at least 'quantity' number of items in a given inventory slot
Index: common/Item.h
===================================================================
--- common/Item.h	(revision 2244)
+++ common/Item.h	(working copy)
@@ -144,7 +144,8 @@
 	inline iter_queue cursor_begin()	{ return m_cursor.begin(); }
 	inline iter_queue cursor_end()		{ return m_cursor.end(); }
 	inline bool CursorEmpty()		{ return (m_cursor.size() == 0); }
-	
+	inline int CursorSize()			{ return m_cursor.size(); }
+
 	// Retrieve a read-only item from inventory
 	inline const ItemInst* operator[](sint16 slot_id) const { return GetItem(slot_id); }
 	
@@ -154,8 +155,8 @@
 	// Add item to cursor queue
 	sint16 PushCursor(const ItemInst& inst);
 	
-	// Swap items in inventory
-	void SwapItem(sint16 slot_a, sint16 slot_b);
+	// Swap items in inventory (Changed to bool return function -U)
+	bool SwapItem(sint16 slot_a, sint16 slot_b);
 
 	// Remove item from inventory
 	bool DeleteItem(sint16 slot_id, uint8 quantity=0);
Index: zone/client.cpp
===================================================================
--- zone/client.cpp	(revision 2244)
+++ zone/client.cpp	(working copy)
@@ -324,6 +324,14 @@
 	}
 	MaxXTargets = 5;	
 	XTargetAutoAddHaters = true;
+
+	// ResyncInventory() Initialization Stuff
+	ri_CallBackTimer = new Timer(0);
+	ri_Count = 0;
+	ri_Step = 0;
+	ri_CursorCount = 0;
+	ri_CallBackDelay = 0;
+
 }
 
 Client::~Client() {
@@ -406,6 +414,7 @@
 	safe_delete(taskstate);
 	safe_delete(KarmaUpdateTimer);
 	safe_delete(GlobalChatLimiterTimer);
+	safe_delete(ri_CallBackTimer); // ResyncInventory() Stuff
 	safe_delete(qGlobals);
 	safe_delete(adventure_request_timer);
 	safe_delete(adventure_create_timer);
Index: zone/client.h
===================================================================
--- zone/client.h	(revision 2244)
+++ zone/client.h	(working copy)
@@ -761,6 +761,7 @@
 	bool	PushItemOnCursor(const ItemInst& inst, bool client_update = false);
 	void	DeleteItemInInventory(sint16 slot_id, sint8 quantity = 0, bool client_update = false, bool update_db = true);
 	bool	SwapItem(MoveItem_Struct* move_in);
+	void	ResyncInventory(bool server_call = true, bool timer_callback = false);
 	void	PutLootInInventory(sint16 slot_id, const ItemInst &inst, ServerLootItem_Struct** bag_item_data = 0);
 	bool	AutoPutLootInInventory(ItemInst& inst, bool try_worn = false, bool try_cursor = true, ServerLootItem_Struct** bag_item_data = 0);
 	void	SummonItem(uint32 item_id, sint16 charges = 0, uint32 aug1=0, uint32 aug2=0, uint32 aug3=0, uint32 aug4=0, uint32 aug5=0, bool attuned=false, uint16 to_slot=SLOT_CURSOR);
@@ -1397,6 +1398,25 @@
 
 	struct XTarget_Struct XTargets[XTARGET_HARDCAP];
 
+	// ResyncInventory() Private Stuff
+	Timer *ri_CallBackTimer;
+	void ResyncInvCallBack();
+	void ResyncInvProcSlots(sint16 slot_begin, sint16 slot_end, int8 sync_method = 128);
+	void ResyncInvClDelItem(sint16 slot_id, bool alt_delete = false);
+	int8 ri_Count;
+	int8 ri_Step;
+	int8 ri_CursorCount;
+	int32 ri_CallBackDelay;
+	// float ri_CallBackDelayModifier = ??
+#define RI_COUNT_LIMIT	3
+#define RI_STEP_COUNT	20
+#define RI_CURSOR_LIMIT	37
+#define RI_BIT_AU		1		// Alternate Update
+#define RI_BIT_UN		2		// Update Null
+#define RI_BIT_AD		4		// Alternate Delete
+#define RI_BIT_CP		8		// Check Parent
+#define RI_BIT_BROKEN	128
+
 };
 
 #include "parser.h"
Index: zone/client_packet.cpp
===================================================================
--- zone/client_packet.cpp	(revision 2244)
+++ zone/client_packet.cpp	(working copy)
@@ -3306,62 +3306,51 @@
 				casting_spell_id);
 			database.SetMQDetectionFlag(AccountName(), GetName(), detect, zone->GetShortName());
 			safe_delete_array(detect);
+			// This Kick() could be deleted unless an automatic ban is in use. (Unremark the code below) -U
 			Kick();   // Kick client to prevent client and server from getting out-of-sync inventory slots
+			//Message(13, "Warning: You have attempted an item move while casting a spell - this action is not allowed!");
+			//ResyncInventory();
 			return;
 		}
 	}
 
-	// Added checks for illegal bagslot swaps..should help with certain cheats (currently checks personal and cursor bag slots.)
-	// - If a player has used an illegal inventory cheat, they can become bugged at some point (especially concerning lore items.)
-	//* Start
+	// Illegal bagslot useage checks. Currently, user only receives a message if this check is triggered. -U
 	bool mi_hack = false;
 
 	if (mi->from_slot >= 251 && mi->from_slot <= 340) {
-		if (mi->from_slot > 330)
-			mi_hack = true; // why are we moving from a cursor bagslot when you can't open it?
+		if (mi->from_slot > 330) { mi_hack = true; } // move << cursor bagslot
 		else {			
-			sint16 from_invslot = Inventory::CalcSlotId(mi->from_slot);
-			const ItemInst *from_invslotitem = GetInv().GetItem(from_invslot); 
+			sint16 from_parent = m_inv.CalcSlotId(mi->from_slot);
 
-			if (!from_invslotitem) // trying to move from bag slots when parent inventory slot is empty
-				mi_hack = true;
-			else if (from_invslotitem->GetItem()->ItemClass == 1) { // checking the parent inventory slot for container
-				if ((Inventory::CalcBagIdx(mi->from_slot) + 1) > from_invslotitem->GetItem()->BagSlots)
-					mi_hack = true; // trying to move from slots beyond parent container size
-			}
-			else // trying to move from bag slots when inventory slot item is not a container
-				mi_hack = true;
+			if (!m_inv[from_parent]) { mi_hack = true; } // move << empty parent
+			else if (m_inv[from_parent]->GetItem()->ItemClass != 1) { mi_hack = true; } // move << non-container parent
+			else if((m_inv.CalcBagIdx(mi->from_slot) + 1) > m_inv[from_parent]->GetItem()->BagSlots) { mi_hack = true; }// move << beyond container size
 		}
 	}
 
 	if (mi->to_slot >= 251 && mi->to_slot <= 340) {
-		if (mi->to_slot > 330)
-			mi_hack = true; // why are we moving to a cursor bagslot when you can't open it?
-		else {
-			sint16 to_invslot = Inventory::CalcSlotId(mi->to_slot);
-			const ItemInst *to_invslotitem = GetInv().GetItem(to_invslot);
+		if (mi->to_slot > 330) { mi_hack = true; } // move >> cursor bagslot
+		else {			
+			sint16 to_parent = m_inv.CalcSlotId(mi->to_slot);
 
-			if (!to_invslotitem) // trying to move into bag slots when parent inventory slot is empty
-				mi_hack = true;
-			else if (to_invslotitem->GetItem()->ItemClass == 1) { // checking the parent inventory slot for container
-				if ((Inventory::CalcBagIdx(mi->to_slot) + 1) > to_invslotitem->GetItem()->BagSlots)
-					mi_hack = true; // trying to move into slots beyond parent container size
-			}
-			else // trying to move into bag slots when inventory slot item is not a container
-				mi_hack = true;
+			if (!m_inv[to_parent]) { mi_hack = true; } // move >> empty parent 
+			else if (m_inv[to_parent]->GetItem()->ItemClass != 1) { mi_hack = true; } // move >> non-container parent
+			else if((m_inv.CalcBagIdx(mi->to_slot) + 1) > m_inv[to_parent]->GetItem()->BagSlots) { mi_hack = true; }// move >> beyond container size
 		}
 	}
 
-	if (mi_hack) { // a CSD can also cause this condition, but more likely the use of a cheat
-		Message(13, "Hack detected: Illegal use of inventory bag slots!");
-		// TODO: Decide whether to log player as hacker - currently has no teeth...
-		// Kick();
-		// return;
-	} // End */
+	if (mi_hack) { Message(15, "Caution: Illegal use of inaccessable bag slots!"); }
 
-	// if this swapitem call fails, then the server and client could be de-sync'd
-	if (!SwapItem(mi) && IsValidSlot(mi->from_slot) && IsValidSlot(mi->to_slot))
-		Message(0, "Client SwapItem request failure - verify inventory integrity.");
+	if (!SwapItem(mi) && IsValidSlot(mi->from_slot) && IsValidSlot(mi->to_slot)) {
+		Message(13, "Client OP_MoveItem request error!");
+		ResyncInventory();
+	
+		if(mi_hack) {
+			Message(15, "Caution: If you use 3rd-party software to manipulate slots,");
+			Message(15, "this action may or may not correct problems in these areas.");
+			Message(15, "To ensure inventory syncronization, you must zone or relog.");
+		}
+	}
 
 	return;
 }
Index: zone/client_process.cpp
===================================================================
--- zone/client_process.cpp	(revision 2244)
+++ zone/client_process.cpp	(working copy)
@@ -746,6 +746,9 @@
 		Message(0,"Your enemies have forgotten you!");
 	}
 	
+	// ResyncInventory() callback timer check
+	if(ri_CallBackTimer && ri_CallBackTimer->Enabled() && ri_CallBackTimer->Check(false)) { ResyncInvCallBack(); }
+
 	return ret;
 }
 
Index: zone/command.cpp
===================================================================
--- zone/command.cpp	(revision 2244)
+++ zone/command.cpp	(working copy)
@@ -459,7 +459,8 @@
 		command_add("xtargets", "Show your targets Extended Targets and optionally set how many xtargets they can have.", 250, command_xtargets) ||
 		command_add("printquestitems","Returns available quest items for multiquesting currently on the target npc.",200,command_printquestitems) ||
 		command_add("clearquestitems","Clears quest items for multiquesting currently on the target npc.",200,command_clearquestitems) ||
-		command_add("zopp", "Troubleshooting command - Sends a fake item packet to you. No server reference is created.", 250, command_zopp) 
+		command_add("zopp", "Troubleshooting command - Sends a fake item packet to you. No server reference is created.", 250, command_zopp) ||
+		command_add("resyncinv", "Client inventory resyncronation command. Use when you suspect an issue with your inventory.", 0, command_resyncinv)
 		)
 	{
 		command_deinit();
@@ -11701,7 +11702,24 @@
 
 		ItemInst* FakeItemInst = database.CreateItem(FakeItem, charges);
 		c->SendItemPacket(slotid, FakeItemInst, packettype);
-		c->Message(0, "Sending zephyr op packet to client - [%s] %s (%u) with %i %s to slot %i.", packettype==ItemPacketTrade?"Trade":"Summon", FakeItem->Name, itemid, charges, abs(charges==1)?"charge":"charges", slotid);
+		c->Message(0, "Sending zephyr op packet to client - [%s] %s (%u), quantity: %i, to slot %i.", packettype==ItemPacketTrade?"Trade":"Summon", FakeItem->Name, itemid, charges, slotid);
 		safe_delete(FakeItemInst);
 	}
 }
+
+void command_resyncinv(Client *c, const Seperator *sep) {
+	// This command can be set to GM status once all of the desync issues are found and corrected -U
+
+	if(!c) { return; }
+	else if(sep->argnum==1 && strcasecmp(sep->arg[1], "help") == 0) {
+		c->Message(0, "This command will attempt to resyncronize your inventory with the server.");
+		c->Message(0, "(Any empty bank slots will return 'lore deletion' messages for each slot)");
+		c->Message(0, "(A 'Worthless Coin' will be left in any empty shared bank slots until you");
+		c->Message(0, "zone or re-log. Clicking this coin will result in another resyncinv call)");
+	}
+	else if(sep->argnum > 0) { c->Message(15, "Usage: #resyncinv [help]"); }
+	else {
+		c->Message(15, "Player initiated inventory resyncronization:");
+		c->ResyncInventory(false);
+	}
+}
Index: zone/command.h
===================================================================
--- zone/command.h	(revision 2244)
+++ zone/command.h	(working copy)
@@ -323,6 +323,7 @@
 void command_printquestitems(Client *c, const Seperator *sep);
 void command_clearquestitems(Client *c, const Seperator *sep);
 void command_zopp(Client *c, const Seperator *sep);
+void command_resyncinv(Client *c, const Seperator *sep);
 
 #ifdef EMBPERL
 void command_embperl_plugin(Client *c, const Seperator *sep);
Index: zone/inventory.cpp
===================================================================
--- zone/inventory.cpp	(revision 2244)
+++ zone/inventory.cpp	(working copy)
@@ -965,6 +965,7 @@
 				banker ? banker->GetName() : "UNKNOWN NPC", distance);
 			database.SetMQDetectionFlag(AccountName(), GetName(), hacked_string, zone->GetShortName());
 			safe_delete_array(hacked_string);
+			// This Kick() could be deleted unless an automatic ban is in use. A resyncinv is triggered on the 'return false;' -U
 			Kick();	// Kicking player to avoid item loss do to client and server inventories not being sync'd
 			return false;
 		}
@@ -1020,12 +1021,16 @@
 			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)) // shouldn't fail because call wouldn't exist otherwise, but just in case...
-				this->Message(13, "Error: Internal SwapItem call returned a failure!");
+			if (!SwapItem(move_in)) {
+#if (EQDEBUG>= 5)
+				LogFile->write(EQEMuLog::Debug, "Client::SwapItem() %s's recursive swapitem call resulted in an unknown failure.", GetName());
+#endif
+			}
 		}
-		Message(13, "Error: Server found no item in slot %i (->%i), Deleting Item!", src_slot_id, dst_slot_id);
-		LogFile->write(EQEMuLog::Debug, "Error: Server found no item in slot %i (->%i), Deleting Item!", src_slot_id, dst_slot_id);
-		this->DeleteItemInInventory(dst_slot_id,0,true);
+		// This code is no longer needed :) -U
+		//Message(13, "Error: Server found no item in slot %i (->%i), Deleting Item!", src_slot_id, dst_slot_id);
+		//LogFile->write(EQEMuLog::Debug, "Error: Server found no item in slot %i (->%i), Deleting Item!", src_slot_id, dst_slot_id);
+		//this->DeleteItemInInventory(dst_slot_id,0,true);
 		return false;
 	}
 	//verify shared bank transactions in the database
@@ -1210,9 +1215,9 @@
 		else {
 			// Nothing in destination slot: split stack into two
 			if ((sint16)move_in->number_in_stack >= src_inst->GetCharges()) {
+				// Move entire stack
+				if(!m_inv.SwapItem(src_slot_id, dst_slot_id)) { return false; }
 				mlog(INVENTORY__SLOTS, "Move entire stack from %d to %d with stack size %d. Dest empty.", src_slot_id, dst_slot_id, move_in->number_in_stack);
-				// Move entire stack
-				m_inv.SwapItem(src_slot_id, dst_slot_id);
 			}
 			else {
 				// Split into two
@@ -1232,8 +1237,8 @@
 			}
 			SetMaterial(dst_slot_id,src_inst->GetItem()->ID);
 		}
+		if(!m_inv.SwapItem(src_slot_id, dst_slot_id)) { return false; }
 		mlog(INVENTORY__SLOTS, "Moving entire item from slot %d to slot %d", src_slot_id, dst_slot_id);
-		m_inv.SwapItem(src_slot_id, dst_slot_id);
 	}
 	
 	int matslot = SlotConvert2(dst_slot_id);
@@ -1258,6 +1263,282 @@
 	return true;
 }
 
+/*
+	This method is used to syncronize client and server inventories when a swapitem request fails.
+	I believe it works as well as it's going to, given the restrictions imposed upon us by the client -U
+
+	See this post for more information:
+	http://www.eqemulator.org/forums/showpost.php?p=213911&postcount=7
+*/
+void Client::ResyncInventory(bool server_call, bool timer_callback) {
+	
+	float delay_modifier = 1.00; // adjust (1/4-point increments) to eliminate timing errors (consider making a rule)
+
+	if(!timer_callback) { /* DO INITIAL CALL WORK */
+		if(!ri_Count) {
+			Message(15, "Attempting to resyncronize %s's inventory - Please stand-by...", GetName());
+			Message(15, "(Do not use your inventory until resyncronization is complete.)");
+
+#if (EQDEBUG>=5)
+			if(server_call) { LogFile->write(EQEMuLog::Debug, "Client::ResyncInventory() server failure of swapitem called for resync of %s's inventory", GetName()); }
+#endif
+		}
+		// Close all containers - if possible
+
+		ri_CallBackTimer->Disable();
+		ri_Count += 1;
+
+		if(ri_Count > RI_COUNT_LIMIT) {
+			ri_Count = 0;
+			ri_Step = 0;
+			ri_CursorCount = 0;
+			ri_CallBackDelay = 0;
+
+			Kick();
+			return;
+		}
+		else if(ri_Count > 1) {
+			Message(13, "Warning: Do not perform any actions until resyncronization completes!");
+			ri_CursorCount = RI_CURSOR_LIMIT;
+		}
+		else {
+			if(m_inv.CursorSize() < RI_CURSOR_LIMIT) { ri_CursorCount = m_inv.CursorSize() + 1; }
+			else { ri_CursorCount = RI_CURSOR_LIMIT; }
+		}
+
+		ri_Step = RI_STEP_COUNT;
+		ri_CallBackDelay = static_cast<int32>(250 * delay_modifier); // Step 1 callback delay (timing critical for proper operation)
+		
+		ri_CallBackTimer->Start(ri_CallBackDelay);
+
+		return;
+	}
+	
+	switch(ri_Step) { /* PARSE WORK TO STEPS FOR EACH CALLBACK */
+		case RI_STEP_COUNT: { // Delete Client CURSOR (Step 1)
+			ResyncInvClDelItem(SLOT_CURSOR);
+
+			return;
+		}
+		case 19: { // Sync WORN & PERSONAL (Step 2)
+			ResyncInvProcSlots(0, 21, RI_BIT_UN);
+			if(GetClientVersion() >= EQClientSoF) { ResyncInvProcSlots(9999, 9999, RI_BIT_UN); }
+			ResyncInvProcSlots(22, 29, RI_BIT_UN);
+
+			ri_CallBackDelay = static_cast<int32>(500 * delay_modifier); // Step 3 callback delay
+			return;
+		}
+		case 18: { // Sync PERSONAL BAGS (Step 3) [First 40 slots]
+			ResyncInvProcSlots(251, 291, (RI_BIT_AU | RI_BIT_UN | RI_BIT_CP));
+
+			ri_CallBackDelay = static_cast<int32>(500 * delay_modifier); // Step 4 callback delay
+			return;
+		}
+		case 17: { // Sync PERSONAL BAGS (Step 4) [Second 40 slots]
+			ResyncInvProcSlots(291, 330, (RI_BIT_AU | RI_BIT_UN | RI_BIT_CP));
+
+			ri_CallBackDelay = static_cast<int32>(500 * delay_modifier); // Step 5 callback delay
+			return;
+		}
+		case 16: { // Sync TRIBUTE (Step 5) /* BROKEN */
+			ResyncInvProcSlots(400, 404, RI_BIT_BROKEN /* (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP) */);
+
+			ri_CallBackDelay = static_cast<int32>(1 /* 1000 working */ * delay_modifier); // Step 6 callback delay
+			return;
+		}
+		case 15: { // Sync BANK (Step 6)
+			if(GetClientVersion() >= EQClientSoF) { ResyncInvProcSlots(2000, 2023, (RI_BIT_UN | RI_BIT_AD)); }
+			else { ResyncInvProcSlots(2000, 2015, (RI_BIT_UN | RI_BIT_AD)); }
+
+			ri_CallBackDelay = static_cast<int32>(1000 * delay_modifier); // Step 7 callback delay
+			return;
+		}
+		case 14: { // Sync BANK BAGS (Step 7) [SoF+ & Ti First 40 slots]
+			if(GetClientVersion() >= EQClientSoF) { ResyncInvProcSlots(2031, 2110, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP)); }
+			else { ResyncInvProcSlots(2031, 2070, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP)); }
+
+			ri_CallBackDelay = static_cast<int32>(500 * delay_modifier); // Step 8 callback delay
+			return;
+		}
+		case 13: { // Sync BANK BAGS (Step 8) [SoF+ & Ti Second 40 slots]
+			if(GetClientVersion() >= EQClientSoF) { ResyncInvProcSlots(2031, 2110, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP)); }
+			else { ResyncInvProcSlots(2071, 2110, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP)); }
+
+			ri_CallBackDelay = static_cast<int32>(500 * delay_modifier); // Step 9 callback delay
+			return;
+		}
+		case 12: { // Sync BANK BAGS (Step 9) [SoF+ & Ti Third 40 slots]
+			if(GetClientVersion() >= EQClientSoF) { ResyncInvProcSlots(2111, 2190, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP)); }
+			else { ResyncInvProcSlots(2111, 2150, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP)); }
+
+			ri_CallBackDelay = static_cast<int32>(500 * delay_modifier); // Step 10 callback delay
+			return;
+		}
+		case 11: { // Sync BANK BAGS (Step 10) [SoF+ & Ti Fourth 40 slots]
+			if(GetClientVersion() >= EQClientSoF) { ResyncInvProcSlots(2111, 2190, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP)); }
+			else { ResyncInvProcSlots(2151, 2190, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP)); }
+
+			ri_CallBackDelay = static_cast<int32>(500 * delay_modifier); // Step 11 callback delay
+			return;
+		}
+		case 10: { // Sync BANK BAGS (Step 11) [SoF+ Fifth 40 slots]
+			if(GetClientVersion() >= EQClientSoF) { ResyncInvProcSlots(2191, 2230, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP)); }
+
+			ri_CallBackDelay = static_cast<int32>(500 * delay_modifier); // Step 12 callback delay
+			return;
+		}
+		case 9: { // Sync BANK BAGS (Step 12) [SoF+ Sixth 40 slots]
+			if(GetClientVersion() >= EQClientSoF) { ResyncInvProcSlots(2231, 2270, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP)); }
+
+			ri_CallBackDelay = static_cast<int32>(500 * delay_modifier); // Step 13 callback delay
+			return;
+		}
+		case 8: { // Sync SHARED BANK & SHARED BANK BAGS (Step 13)
+			ResyncInvProcSlots(2500, 2501, (RI_BIT_UN | RI_BIT_AD));
+			ResyncInvProcSlots(2531, 2550, (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP));
+			
+			ri_CallBackDelay = static_cast<int32>(1000 * delay_modifier); // Step 14 callback delay
+			return;
+		}
+		case 7: { // Sync TRADER (Step 14) /* BROKEN */
+			ResyncInvProcSlots(3000, 3007, RI_BIT_BROKEN /* (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP) */);
+			
+			ri_CallBackDelay = static_cast<int32>(1 /* 1000 working */ * delay_modifier); // Step 15 callback delay
+			return;
+		}
+		case 6: { // Sync TRADER BAGS (Step 15) [First 40 Slots] /* BROKEN */
+			ResyncInvProcSlots(3100, 3139, RI_BIT_BROKEN /* (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP) */);
+
+			ri_CallBackDelay = static_cast<int32>(1 /* 500 working */ * delay_modifier); // Step 16 callback delay
+			return;
+		}
+		case 5: { // Sync TRADER BAGS (Step 16) [Second 40 slots] /* BROKEN */
+			ResyncInvProcSlots(3140, 3179, RI_BIT_BROKEN /* (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP) */);
+
+			ri_CallBackDelay = static_cast<int32>(1 /* 500 working */ * delay_modifier); // Step 17 callback delay
+			return;
+		}
+		case 4: { // Sync WORLD CONTAINER (Step 17) /* BROKEN */
+			ResyncInvProcSlots(4000, 4009, RI_BIT_BROKEN /* (RI_BIT_AU | RI_BIT_UN | RI_BIT_AD | RI_BIT_CP) */);
+			
+			ri_CallBackDelay = static_cast<int32>(1 /* 1000 working */ * delay_modifier); // Step 18 callback delay
+			return;
+		}
+		case 3: { // Delete Client CURSOR (Step 18)
+			ResyncInvClDelItem(SLOT_CURSOR, true); // hack for Bank Slots
+
+			ri_CallBackDelay = static_cast<int32>(1500 * delay_modifier); // Step 19 callback delay
+			return;
+		}
+		case 2: { // Sync Client CURSOR (Step 19)
+			ResyncInvClDelItem(SLOT_CURSOR); // hack for Bank Slots
+
+			ri_CursorCount = 1;
+			for(std::list<ItemInst*>::const_iterator cursor_iter = m_inv.cursor_begin(); cursor_iter != m_inv.cursor_end() && ri_CursorCount <= RI_CURSOR_LIMIT;
+				cursor_iter++, ri_CursorCount++) { SendItemPacket(SLOT_CURSOR, *cursor_iter, ItemPacketSummonItem); }
+
+			if(ri_Count > 1) { ri_CallBackDelay = static_cast<int32>(4000 * delay_modifier); } // Step 20 callback delay (full) }
+			else { ri_CallBackDelay = static_cast<int32>(2000 * delay_modifier); } // Step 20 callback delay (normal)
+			return;
+		}
+		case 1: { // Sync Cursor Bags (Step 20)
+			ResyncInvProcSlots(331, 340, (RI_BIT_AU | RI_BIT_UN | RI_BIT_CP));
+			
+			ri_CallBackDelay = static_cast<int32>(100 * delay_modifier); // Finalization callback delay
+			return;
+		}
+		case 0: { // Finalization
+			if (ri_CursorCount > RI_CURSOR_LIMIT) {
+				Message(15, "Caution: The server's cursor count exceeds client capacity.");
+				Message(15, "Remove all cursor items and resycinv again to correct this.");
+			}
+
+			ri_Count = 0;
+			ri_Step = 0;
+			ri_CursorCount = 0;
+			ri_CallBackDelay = 0;
+
+			Message(14, "%s's inventory resyncronization complete.", GetName());
+			Message(0, "(Close any container/trade windows that were open before using inventory) ");
+			return;
+		}
+		default: { // (somehow, 'ResyncInvStep' was set above the number of coded steps...)
+#if (EQDEBUG>=5)
+			LogFile->write(EQEMuLog::Debug, "Client::ResyncInventory() resyncinv for %s's inventory tried to process non-existent step %i", GetName(), ri_Step);
+#endif
+
+			ri_Count = 0;
+			ri_Step = 0;
+			ri_CursorCount = 0;
+			ri_CallBackDelay = 0;
+
+			Kick();
+			return;
+		}
+	}
+}
+
+void Client::ResyncInvCallBack() {
+
+	int8 step_percent = ((static_cast<float>(RI_STEP_COUNT - ri_Step) / RI_STEP_COUNT) * 100);
+	if(step_percent) { Message(1, "Resycronization %i percent complete...", step_percent); }
+
+	ri_CallBackTimer->Disable();
+	ResyncInventory(false, true);
+	
+	if(ri_CursorCount && ri_Step == RI_STEP_COUNT) { ri_CursorCount -= 1; }
+	else if(ri_Step) { ri_Step -= 1; }
+
+	if(ri_CallBackDelay) { ri_CallBackTimer->Start(ri_CallBackDelay); }
+}
+
+void Client::ResyncInvProcSlots(sint16 slot_begin, sint16 slot_end, int8 sync_method) {
+
+	if(sync_method & RI_BIT_BROKEN) { return; }
+
+	for(sint16 slot_id = slot_begin; slot_id <= slot_end; slot_id++) {
+		const ItemInst* slot_inst = m_inv[slot_id];
+
+		if(sync_method & RI_BIT_CP) {
+			sint16 parent_slot = m_inv.CalcSlotId(slot_id);
+
+			if(parent_slot != SLOT_INVALID && m_inv[parent_slot] && m_inv[parent_slot]->GetItem()->ItemClass == 1) {
+				if(slot_inst) { SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }
+				else { if(!(sync_method & RI_BIT_AU) && (sync_method & RI_BIT_UN)) { ResyncInvClDelItem(slot_id, (sync_method & RI_BIT_AD)); } }
+			}
+		}
+		else {
+			if(slot_inst) { SendItemPacket(slot_id, slot_inst, ItemPacketTrade); }
+			else { if(!(sync_method & RI_BIT_AU) && (sync_method & RI_BIT_UN)) { ResyncInvClDelItem(slot_id, (sync_method & RI_BIT_AD)); } }
+		}
+	}
+}
+
+void Client::ResyncInvClDelItem(sint16 slot_id, bool alt_delete) {
+
+	if(alt_delete) {
+		const Item_Struct* ri_TokenStruct = database.GetItem(1041); // 'Worthless Coin'
+		ItemInst* ri_TokenInst = database.CreateItem(ri_TokenStruct, 1);
+		
+		SendItemPacket(slot_id, ri_TokenInst, ItemPacketTrade);
+	}
+	else {
+		const Item_Struct* ri_TokenStruct = database.GetItem(22292); // 'Copper Coin'
+		ItemInst* ri_TokenInst = database.CreateItem(ri_TokenStruct, 1);
+
+		SendItemPacket(slot_id, ri_TokenInst, ItemPacketTrade);
+
+		EQApplicationPacket* outapp		= new EQApplicationPacket(OP_DeleteItem, sizeof(DeleteItem_Struct));
+		DeleteItem_Struct* delete_slot	= (DeleteItem_Struct*)outapp->pBuffer;
+		delete_slot->from_slot			= slot_id;
+		delete_slot->to_slot			= 0xFFFFFFFF;
+		delete_slot->number_in_stack	= 0xFFFFFFFF;
+		
+		QueuePacket(outapp);
+		safe_delete(outapp);
+	}	
+}
+
 void Client::DyeArmor(DyeStruct* dye){
 	sint16 slot=0;
 	for(int i=0;i<7;i++){
__________________
Uleat of Bertoxxulous

Compilin' Dirty
Reply With Quote