Discussion: 
http://eqemulator.net/forums/showthread.php?t=26177
Please forgive the indent loss.  This should be straight-forward.  The last modification in Doors::HandleClick, the /*NEW*/ lines indicate what to add, just look for the start and don't miss the curly braces.
==================================================  ========================
[in SQL]
	Quote:
	
	
		
			
				drop table if exists `keyring`; 
create table `keyring`( 
  `char_id` integer not null, 
  `item_id` integer not null 
) engine=MyISAM DEFAULT CHARSET=latin1;
			
		 | 
	
	
 ==================================================  ========================
[in ./common/database.cpp add to method Database:

eleteCharacter(char *name)]:
	Quote:
	
	
		
			
				#if DEBUG >= 5 
	printf(" keyring"); 
#endif 
	RunQuery(query, MakeAnyLenString(&query, "DELETE FROM keyring WHERE char_id='%d'", charid), errbuf, NULL, &affected_rows); 
	if(query) 
	{ 
		safe_delete_array(query); 
		query = NULL; 
	}
			
		 | 
	
	
 ==================================================  ========================
[in ./zone/client.h add to header:]
==================================================  ========================
[in ./zone/client.h add to private section of Client class definition:]
	Quote:
	
	
		| 
			
				 std::list<int32> keyring;
			
		 | 
	
	
 ==================================================  ========================
[in ./zone/client.h add to public section of Client class definition:]
	Quote:
	
	
		
			
				void	KeyRingLoad(); 
void	KeyRingAdd(int32 item_id); 
bool	KeyRingCheck(int32 item_id); 
void	KeyRingList();
			
		 | 
	
	
 ==================================================  ========================
[in ./zone/client.cpp add to constructor:]
==================================================  ========================
[in ./zone/client.cpp add:]
	Quote:
	
	
		
			
				void Client::KeyRingLoad() 
{ 
	char errbuf[MYSQL_ERRMSG_SIZE]; 
	char *query = 0; 
	MYSQL_RES *result; 
	MYSQL_ROW row; 
	query = new char[256]; 
 
	keyring.clear(); 
	sprintf(query, "SELECT item_id FROM keyring WHERE char_id='%i' ORDER BY item_id",character_id); 
	if (database.RunQuery(query, strlen(query), errbuf, &result))  
	{ 
		safe_delete_array(query); 
		while(0 != (row = mysql_fetch_row(result))){ 
			keyring.push_back(atoi(row[0])); 
		} 
		mysql_free_result(result); 
	}else { 
		cerr << "Error in Client::KeyRingLoad query '" << query << "' " << errbuf << endl; 
		safe_delete_array(query); 
		return; 
	} 
} 
 
void Client::KeyRingAdd(int32 item_id) 
{ 
	char errbuf[MYSQL_ERRMSG_SIZE]; 
	char *query = 0; 
	int32 affected_rows = 0; 
	query = new char[256]; 
	bool bFound = KeyRingCheck(item_id); 
	if(!bFound){ 
		sprintf(query, "INSERT INTO keyring(char_id,item_id) VALUES(%i,%i)",character_id,item_id); 
		if(database.RunQuery(query, strlen(query), errbuf, 0, &affected_rows)) 
		{ 
			Message(4,"Added to keyring."); 
			safe_delete_array(query); 
		} 
		else 
		{ 
			cerr << "Error in Doors::HandleClick query '" << query << "' " << errbuf << endl; 
			safe_delete_array(query); 
			return; 
		} 
		keyring.push_back(item_id); 
	} 
} 
 
bool Client::KeyRingCheck(int32 item_id) 
{ 
	for(std::list<int32>::iterator iter = keyring.begin(); 
		iter != keyring.end(); 
		++iter) 
	{ 
		if(*iter == item_id) 
			return true; 
	} 
	return false; 
} 
 
void Client::KeyRingList() 
{ 
	Message(4,"Keys on Keyring:"); 
	const Item_Struct *item = 0; 
	for(std::list<int32>::iterator iter = keyring.begin(); 
		iter != keyring.end(); 
		++iter) 
	{ 
		if ((item = database.GetItem(*iter))!=NULL) { 
			Message(4,item->Name); 
		} 
	} 
}
			
		 | 
	
	
 ==================================================  ========================
[in ./common/emu_oplist.h add]
==================================================  ========================
[in patch_Titanium.conf add]
==================================================  ========================
[in ./zone/client_packet.h add]
	Quote:
	
	
		| 
			
				void Handle_OP_KeyRing(const EQApplicationPacket *app);
			
		 | 
	
	
 ==================================================  ========================
[in ./zone/client_packet.cpp in function MapOpcodes() add]
	Quote:
	
	
		| 
			
				ConnectedOpcodes[OP_KeyRing] = &Client::Handle_OP_KeyRing;
			
		 | 
	
	
 ==================================================  ========================
[in ./zone/client_packet.cpp add]
	Quote:
	
	
		
			
				void Client::Handle_OP_KeyRing(const EQApplicationPacket *app) 
{ 
	KeyRingList(); 
}
			
		 | 
	
	
 ==================================================  ========================
[in ./zone/client_packet.cpp, insert in Client::FinishConnState2() just after SetEndurance():]
==================================================  ========================
[in ./zone/doors.cpp in method Doors::HandleClick(...) add code on lines]
	Quote:
	
	
		
			
					    if (sender->GetGM())		// GM can always open locks - should probably be changed to require a key 
		{ 
			sender->Message_StringID(4,DOORS_GM); 
			if( !IsDoorOpen() || opentype == 58 ) 
			{  
				md->action = OPEN_DOOR;  
			}  
			else 
			{  
				md->action = CLOSE_DOOR;  
			}  
		} 
		else if (playerkey) 
		{	// they have something they are trying to open it with 
			if (keyneeded && keyneeded == playerkey) 
			{	// key required and client is using the right key  
/*NEW*/				sender->KeyRingAdd(playerkey); 
				sender->Message(4,"You got it open!");		// more debug spam 
				if( !IsDoorOpen() || opentype == 58 ) 
				{  
					md->action = OPEN_DOOR;  
				}  
				else 
				{  
					md->action = CLOSE_DOOR;  
				}  
			} 
		} 
		else if(lockpicks != NULL) 
		{ 
			if(sender->GetSkill(PICK_LOCK)) 
			{ 
				if(lockpicks->GetItem()->ItemType == ItemTypeLockPick) 
				{ 
					float modskill=sender->GetSkill(PICK_LOCK); 
					sender->CheckIncreaseSkill(PICK_LOCK, 1); 
#if EQDEBUG>=5 
					LogFile->write(EQEMuLog: ebug,"Client has lockpicks: skill=%f", modskill); 
#endif 
 
					if(GetLockpick() <= modskill) 
					{ 
						if(!IsDoorOpen()) 
						{  
							md->action = OPEN_DOOR;  
						}  
						else 
						{  
							md->action = CLOSE_DOOR;  
						} 
						sender->Message_StringID(4,DOORS_SUCCESSFUL_PICK); 
					} 
					else 
					{ 
						sender->Message_StringID(4,DOORS_INSUFFICIENT_SKILL); 
						return; 
					} 
				} 
				else 
				{ 
					sender->Message_StringID(4,DOORS_NO_PICK); 
					return; 
				} 
			} 
			else 
			{ 
				sender->Message_StringID(4,DOORS_CANT_PICK); 
				return; 
			} 
		} 
		else 
		{	// locked door and nothing to open it with 
/*NEW*/			if(keyneeded != 0){ 
/*NEW*/				//search for key on keyring 
/*NEW*/				if(sender->KeyRingCheck(keyneeded)){ 
/*NEW*/					sender->Message(4,"You got it open!");		// more debug spam 
/*NEW*/					if( !IsDoorOpen() || opentype == 58 ) 
/*NEW*/					{  
/*NEW*/						md->action = OPEN_DOOR;  
/*NEW*/					}  
/*NEW*/					else 
/*NEW*/					{  
/*NEW*/						md->action = CLOSE_DOOR;  
/*NEW*/					}  
/*NEW*/				} 
/*NEW*/			} 
/*NEW*/			else 
/*NEW*/			{ 
				sender->Message_StringID(4,DOORS_LOCKED); 
				return; 
/*NEW*/			} 
		}
			
		 | 
	
	
 ==================================================  ========================