PDA

View Full Version : Shopkeeper Code


darvik
01-28-2002, 11:59 AM
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

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

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

darvik
01-28-2002, 12:03 PM
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 :)


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

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

#
# 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);

DeletedUser
01-28-2002, 12:24 PM
Good job looks great :)

Yodason
01-28-2002, 01:29 PM
WOW! Nice job!

Zeitgeist
01-28-2002, 04:13 PM
me getting feeling that .2.1 will be *ahem* a m*th*rf*ck*r =)

Pyrotek
01-29-2002, 12:42 AM
Graar.. I was working on these structs too. hehe... Good job, i'll see if we've found anything different.

Drawde
01-29-2002, 01:18 AM
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 :)

darvik
01-29-2002, 03:19 AM
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..

Ukyo
01-29-2002, 03:30 AM
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

DeletedUser
01-29-2002, 09:30 AM
I will get these into the next release...

Drawde
01-30-2002, 08:13 AM
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?

Yodason
02-03-2002, 06:18 AM
I am currently working on the database code.