|  |  | 
 
  |  |  |  |  
  |  |  |  |  
  |  |  |  |  
  |  |  |  |  
  |  | 
	
		
   
   
      | Archive::Development Archive area for Development's posts that were moved here after an inactivity period of 90 days. |  
	
	
		
	
	
 
  |  |  |  |  
	| 
			
			 
			
				01-28-2002, 11:59 AM
			
			
			
		 |  
	| 
		
			
			| Fire Beetle |  | 
					Join Date: Jan 2002 
						Posts: 21
					      |  |  
	| 
				 Shopkeeper Code 
 Been doing some adds to the code - based off 0.2.0 , added lots of stuff for merchant interaction, but it's far from complete.  I coulnd't figure out how to do the database stuff right, so basicly the item list is hardcoded for all merchants, they all sell the same 2 items, and no matter what you buy, you get grapes.  You can sell stuff and get money though.. and the client no longer locks up when you try to interact with a merchant. 
Had to figure out a lot of this stuff out on my own because nobody else has done it before.. the opCodes were not even in showEQ.
 
Anyway - someone else will have to do the database portion of this -- I also included some sample sql of how this can be done without having to modify the npc_types table.
 
Have to split code into seperate posts ;/
 
code for eq_opcodes.h
 
	Code: // Darvik: for shopkeepers
#define OP_ShopRequest     0x0b20 // right-click on merchant
#define OP_ShopItem        0x0c20
#define OP_ShopWelcome     0x1420
#define OP_ShopPlayerBuy   0x3520
#define OP_ShopGiveItem    0x3120
#define OP_ShopTakeMoney   0x3520
#define OP_ShopPlayerSell  0x2720
#define OP_ShopEnd         0x3720
#define OP_ShopEndConfirm  0x4521 code for client_process.cpp - I added this just above case OP_Jump:
 
	Code: case OP_ShopRequest:
{
	Merchant_Click_Struct* mc=(Merchant_Click_Struct*)app->pBuffer;
	if (app->size == sizeof(Merchant_Click_Struct))
	{
		cout << name << " is at merchant. ID:" << mc->merchantid << endl;
		DumpPacketHex(app);
		int32 mi = mc->merchantid;
		// Send back opcode OP_ShopRequest - tells client to open merchant window.
		APPLAYER* outapp = new APPLAYER;
		outapp->opcode = OP_ShopRequest;
		outapp->size = sizeof(Merchant_Click_Struct);
		outapp->pBuffer = new uchar[sizeof(Merchant_Click_Struct)];
		Merchant_Click_Struct* mco=(Merchant_Click_Struct*)outapp->pBuffer;
		mco->mcs_unknown001 = mc->mcs_unknown001;
		mco->merchantid = mc->merchantid;
		mco->itemslot = 0x01;			// unused here, but we set to a harmless value.
		mco->mcs_unknown002[0] = mc->mcs_unknown002[0];
		mco->mcs_unknown002[1] = mc->mcs_unknown002[0];
		mco->mcs_unknown002[2] = mc->mcs_unknown002[0];
		mco->quantity = 0x1b;			// qty when used in purchase, unused here.
		mco->mcs_unknown003[0] = 0x0d;	// unknown data - has something to do with
		mco->mcs_unknown003[1] = 0xa0;	// buying and selling cost or markup.
		mco->mcs_unknown003[2] = 0x3F;	// <- changing this value even slightly severly screws up price.
		cout << "sending back merchant_click_struct to client." << endl;
		DumpPacketHex(outapp);
		QueuePacket(outapp);
		delete outapp;
							
		// TODO: Lookup	merchants wares..
		// TODO: Start Loop through wares..
			// Debug - hardcoded short beer .. trying to get stackable items to work.
			uint16 item_nr = 16592;
			Item_Struct* item = database.GetItem(item_nr);
			cout << "merchant is selling: " << item->name << endl;
			// Send this item to the client.
			outapp = new APPLAYER;
			outapp->opcode = OP_ShopItem;
			outapp->size = sizeof(Item_Shop_Struct);
			outapp->pBuffer = new uchar[outapp->size];
			Item_Shop_Struct* iss = (Item_Shop_Struct*)outapp->pBuffer;
			iss->merchantid = mi;
			iss->itemtype = 0x00;			// TODO: needs to be parsed from item.
			item->equipSlot = 0x00;			// this needs to be incremented in loop.
			memcpy(&iss->item, item, sizeof(Item_Struct));
			iss->iss_unknown001 = 0;
			QueuePacket(outapp);
			delete outapp;
			// Debug - hardcoded cloth cap..
			item_nr = 1001;
			item = database.GetItem(item_nr);
			cout << "merchant is selling: " << item->name << endl;
			// Send this item to the client.
			outapp = new APPLAYER;
			outapp->opcode = OP_ShopItem;
			outapp->size = sizeof(Item_Shop_Struct);
			outapp->pBuffer = new uchar[outapp->size];
			iss = (Item_Shop_Struct*)outapp->pBuffer;
			iss->merchantid = mi;
			iss->itemtype = 0x01;			// TODO: needs to be parsed from item.
			item->equipSlot = 0x01;			// this needs to be incremented in loop.
			item->common.number = 1;		// not sure if this tells how many the merchant has.
			memcpy(&iss->item, item, sizeof(Item_Struct));
			iss->iss_unknown001 = 0;
			QueuePacket(outapp);
			delete outapp;
		// TODO: End Loop through wares..
		// say welcome to player - dont know c well enough to do this right.
		// it's just a single null terminated string, heh.
		outapp = new APPLAYER;
		outapp->opcode = OP_ShopWelcome;
		outapp->pBuffer = new uchar[12];
		outapp->size = 12;
		outapp->pBuffer[0] = 0x48;
		outapp->pBuffer[1] = 0x65;
		outapp->pBuffer[2] = 0x6c;
		outapp->pBuffer[3] = 0x6c;
		outapp->pBuffer[4] = 0x6f;
		outapp->pBuffer[5] = 0x20;
		outapp->pBuffer[6] = 0x74;
		outapp->pBuffer[7] = 0x68;
		outapp->pBuffer[8] = 0x65;
		outapp->pBuffer[9] = 0x72;
		outapp->pBuffer[10] = 0x65;
		outapp->pBuffer[11] = 0x00;	
		QueuePacket(outapp);
		delete outapp;
	}
	else
	{
		cout << "Wrong size" << app->size  << ", should be " << sizeof(Merchant_Click_Struct) << endl;
	}
	break;
} Will follow this post with the rest of the opCodes..
			
			
			
			
				  |  
 
  |  |  |  |  
	
		
	
	
 
  |  |  |  |  
	| 
			
			 
			
				01-28-2002, 12:03 PM
			
			
			
		 |  
	| 
		
			
			| Fire Beetle |  | 
					Join Date: Jan 2002 
						Posts: 21
					      |  |  
	| 
				  
 Rest of the OpCodes , the structs, and the SQL.. 
Yes, code is a little sloppy, can be cleaned up a bit when databse stuff is implemented    
	Code: case OP_ShopPlayerBuy:
{
	cout << name << " is attempting to purchase an item..  " << endl;
	Merchant_Purchase_Struct* mp=(Merchant_Purchase_Struct*)app->pBuffer;
	// this item number needs to bounce back to the merchant table, since the client
	// only tells us what slot the client is wanting to purchase.
	// slot value is in mp->itemslot.
	uint16 item_nr = 16592;	// hardcoded to always sell you grapes :)
	Item_Struct* item = database.GetItem(item_nr);
						
	// Give Item
	APPLAYER* outapp = new APPLAYER;
	outapp->opcode = OP_ShopGiveItem;
	outapp->size = sizeof(Item_Struct);
	outapp->pBuffer = new uchar[outapp->size];
	item->common.number = mp->merchant.quantity;		// number purchased from merchant.
	// copied this code from loot code - but doesn't seem to work right.
	item->equipSlot = 0xFF;
	for (int i=22; i<31; i++)
	{
		if (pp.inventory[i] == 0xFFFF || pp.inventory[i] == item->item_nr)
		{
			item->equipSlot = i;
			break;
		}
	}
	if (item->equipSlot == 0xFF)
	{
		Message(14,"There is no more room in your inventory. Item cannot be purchased.");
							delete outapp;
	}
	else
	{
		//item->equipSlot = 0x1d;
		memcpy(outapp->pBuffer, item, sizeof(Item_Struct));
		QueuePacket(outapp);
		delete outapp;
		// Take Players Money
		outapp = new APPLAYER;
		outapp->opcode = OP_ShopTakeMoney;
		outapp->size = sizeof(Merchant_Purchase_Struct);
		outapp->pBuffer = new uchar[outapp->size];
		Merchant_Purchase_Struct* mps = (Merchant_Purchase_Struct*)outapp->pBuffer;
		mps->merchant.mcs_unknown001 = mp->merchant.mcs_unknown001;
		mps->merchant.merchantid = mp->merchant.merchantid;
		mps->merchant.itemslot = mp->merchant.itemslot;
		mps->merchant.mcs_unknown002[0] = 0x00;
		mps->merchant.mcs_unknown002[1] = 0x00;
		mps->merchant.mcs_unknown002[2] = 0x00;
		mps->merchant.quantity = mp->merchant.quantity;
		mps->merchant.mcs_unknown003[0] = 0x00;
		mps->merchant.mcs_unknown003[1] = 0x00;
		mps->merchant.mcs_unknown003[2] = 0x00;
		mps->mps_unknown001 = item->cost;			// amount in copper.
		cout << "taking " << item->cost << " copper from " << name << "." << endl;
		DumpPacketHex(outapp);
		QueuePacket(outapp);
		delete outapp;
	}
	break;
}
case OP_ShopPlayerSell:
{
	cout << name << " is trying to sell an item." << endl;
	DumpPacketHex(app);
	Merchant_Purchase_Struct* mp=(Merchant_Purchase_Struct*)app->pBuffer;
	APPLAYER* outapp = new APPLAYER;
	outapp->opcode = OP_ShopPlayerSell;
	outapp->size = sizeof(Merchant_Purchase_Struct);
	outapp->pBuffer = new uchar[outapp->size];
	Merchant_Purchase_Struct* mps = (Merchant_Purchase_Struct*)outapp->pBuffer;
	mps->merchant.mcs_unknown001	= mp->merchant.mcs_unknown001;
	mps->merchant.merchantid		= mp->merchant.merchantid;
	mps->merchant.itemslot			= mp->merchant.itemslot;
	mps->merchant.mcs_unknown002[0] = 0x00;
	mps->merchant.mcs_unknown002[1] = 0x46;
	mps->merchant.mcs_unknown002[2] = 0x00;
	mps->merchant.quantity			= 0x00;
	mps->merchant.mcs_unknown003[0] = 0x00;
	mps->merchant.mcs_unknown003[1] = 0x00;
	mps->merchant.mcs_unknown003[2] = 0x00;
	mps->mps_unknown001				= 0x00;
	cout << "response from sell action.." << endl;
	DumpPacketHex(outapp);
	QueuePacket(outapp);
	delete outapp;
	break;
}
case OP_ShopEnd:
{
	cout << name << " is ending merchant interaction." << endl;
	APPLAYER* outapp = new APPLAYER;
	outapp->opcode = OP_ShopEndConfirm;
	outapp->pBuffer = new uchar[2];
	outapp->size = 2;
	outapp->pBuffer[0] = 0x0a;
	outapp->pBuffer[1] = 0x66;
	QueuePacket(outapp);
	delete outapp;
	break;
} code for eq_packet_structs.h
 
	Code: // Darvik: shopkeeper structs
struct Merchant_Click_Struct {
	uint32 mcs_unknown001;		// unknown - first byte has different value by zone??
	uint32 merchantid;			// unique identifier for merchantID
	int8 itemslot;				// always 0x40 FROM client, 0x01 TO client. itemslot when used in purchase. 
	int8 mcs_unknown002[3];		// always 0x0b, 0x7c, 0x00 ??
	int8 quantity;				// Qty - when used in Merchant_Purchase_Struct
	int8 mcs_unknown003[3];		// always 0xa5, 0x3f ??
};
struct Merchant_Purchase_Struct {
	Merchant_Click_Struct merchant;
	int32 mps_unknown001;		// Cost in copper
};
struct Item_Shop_Struct {
	uint32 merchantid;
	int8 itemtype;
	Item_Struct item;
	int32 iss_unknown001;
}; 
SQL stuff:
 
	Code: #
# Table structure for table 'shopkeepers'
#
CREATE TABLE shopkeepers (
  id int(11) NOT NULL,
  npc_id int(11) NOT NULL,
  items_table int(11) NOT NULL,
  price_markup tinyint(2) unsigned NOT NULL default '0',
  PRIMARY KEY  (id, npc_id)
) TYPE=MyISAM;
#
# Table structure for table 'shopkeeper_items'
#
CREATE TABLE shopkeeper_items (
  id int(11) NOT NULL,
  seq tinyint(2) unsigned NOT NULL,
  item_id int(11) NOT NULL,
  qty int(11) NOT NULL,
  PRIMARY KEY (id,seq)
) TYPE=MyISAM;
#
# Dumping data for table 'shopkeepers'
#
INSERT INTO shopkeepers VALUES (1,2,1,0);
#
# Dumping data for table 'shopkeeper_items'
#
INSERT INTO shopkeeper_items VALUES (1,0,1001,99);
INSERT INTO shopkeeper_items VALUES (1,1,1002,99);
INSERT INTO shopkeeper_items VALUES (1,2,1003,99);
INSERT INTO shopkeeper_items VALUES (1,3,1004,99);
INSERT INTO shopkeeper_items VALUES (1,4,1005,99);
INSERT INTO shopkeeper_items VALUES (1,5,1006,99);
INSERT INTO shopkeeper_items VALUES (1,6,1007,99);
INSERT INTO shopkeeper_items VALUES (1,7,1008,99);
INSERT INTO shopkeeper_items VALUES (1,8,1009,99);
INSERT INTO shopkeeper_items VALUES (1,9,1010,99);
INSERT INTO shopkeeper_items VALUES (1,10,1011,99);
INSERT INTO shopkeeper_items VALUES (1,11,1012,99);
			
			
			
			
				  |  
 
  |  |  |  |  
	
		
	
	
	| 
			
			 
			
				01-28-2002, 12:24 PM
			
			
			
		 |  
	| 
		
			
			| Fire Beetle |  | 
					Join Date: Sep 2002 
						Posts: 0
					      |  |  
	| 
 Good job looks great   |  
	
		
	
	
	| 
			
			 
			
				01-28-2002, 01:29 PM
			
			
			
		 |  
	| 
		
			
			| Hill Giant |  | 
					Join Date: Jan 2002 
						Posts: 205
					      |  |  
	| 
 WOW! Nice job! |  
	
		
	
	
	| 
			
			 
			
				01-28-2002, 04:13 PM
			
			
			
		 |  
	| 
		
			
			| Discordant |  | 
					Join Date: Jan 2002 
						Posts: 289
					      |  |  
	| 
 me getting feeling that .2.1 will be *ahem* a m*th*rf*ck*r =) 
				__________________gm-Zeitgeist
 I WAS Diligently Working at the Next Board Title :p
 webmaster godmonkey.com, dreamusher.com
 |  
	
		
	
	
	| 
			
			 
			
				01-29-2002, 12:42 AM
			
			
			
		 |  
	| 
		
			
			| Sarnak |  | 
					Join Date: Jan 2002 
						Posts: 97
					      |  |  
	| 
 Graar..  I was working on these structs too.  hehe...  Good job, i'll see if we've found anything different. |  
	
		
	
	
	| 
			
			 
			
				01-29-2002, 01:18 AM
			
			
			
		 |  
	| 
		
			
			| Dragon |  | 
					Join Date: Jan 2002 
						Posts: 521
					      |  |  
	| 
 Wow, great work! 
Will this code be included in the next release of the emulator?
 
This is just my personal opinion, but I think it might be better to have a merchant_id or similar value in npc_types, rather than a npc_type value in the merchant data. This is just from the point of view of adding merchant info to my spawn data, since my npc_type data is output by a parser program rather than written by hand, the ID of a particular NPC might change from version to version depending on what I add. Just my opinion though.
 
Unless you're brewing a lot of wine, however, it might be good if database merchant lists can be added so you can buy something other than grapes   |  
	
		
	
	
	| 
			
			 
			
				01-29-2002, 03:19 AM
			
			
			
		 |  
	| 
		
			
			| Fire Beetle |  | 
					Join Date: Jan 2002 
						Posts: 21
					      |  |  
	| 
 Yeah, I agree, the merchant_id idea was just something to avoid having to edit the npc tables and stuff .. really all you need to add into the npc_type table is probably just a merchant_table_id which refrences the list of items they sell (since multiple merchants may sell the same stuff - like a general merchant) - you can still use the table that I listed for merchant items though - I'm pretty sure I didn't miss anything required there.. although I haven't figured out what tells a merchant how many of a particular item they have.. like when a player sells something, and then you buy it back .. it's probably server-side , and there's an opcode that I missed that tells the client to delete the item from the list.. maybe I'll check into that tonight.. |  
	
		
	
	
	| 
			
			 
			
				01-29-2002, 03:30 AM
			
			
			
		 |  
	| 
		
			
			| Fire Beetle |  | 
					Join Date: Jan 2002 
						Posts: 7
					      |  |  
	| 
 From what I have seen, items are set as X for infinite amount, or # with some regen time. At least, that's how it always seemed to me.
 -Ukyo
 |  
	
		
	
	
	| 
			
			 
			
				01-29-2002, 09:30 AM
			
			
			
		 |  
	| 
		
			
			| Fire Beetle |  | 
					Join Date: Sep 2002 
						Posts: 0
					      |  |  
	| 
 I will get these into the next release... |  
	
		
	
	
	| 
			
			 
			
				01-30-2002, 08:13 AM
			
			
			
		 |  
	| 
		
			
			| Dragon |  | 
					Join Date: Jan 2002 
						Posts: 521
					      |  |  
	| 
 I hope this isn't too much bother, but could you post the database table structs that you are going to use? That way I can start work on merchant data before the next emulator release.Will you be using a merchant list with NPC IDs, or a merchant ID in the npc_types struct?
 |  
	
		
	
	
	| 
			
			 
			
				02-03-2002, 06:18 AM
			
			
			
		 |  
	| 
		
			
			| Hill Giant |  | 
					Join Date: Jan 2002 
						Posts: 205
					      |  |  
	| 
 I am currently working on the database code. |  
	
		
	
	
	
	
	| 
	|  Posting Rules |  
	| 
		
		You may not post new threads You may not post replies You may not post attachments You may not edit your posts 
 HTML code is Off 
 |  |  |  All times are GMT -4. The time now is 07:42 PM.
 
 |  |  
    |  |  |  |  
    |  |  |  |  
     |  |  |  |  
 |  |