EQEmulator Forums

EQEmulator Forums (https://www.eqemulator.org/forums/index.php)
-   Archive::Development (https://www.eqemulator.org/forums/forumdisplay.php?f=621)
-   -   Shopkeeper Code (https://www.eqemulator.org/forums/showthread.php?t=151)

darvik 01-28-2002 11:59 AM

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

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

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


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.


All times are GMT -4. The time now is 07:46 AM.

Powered by vBulletin®, Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.