[C++, SQL] Multivendor
Hello there,
Id like to present you one of scripts from my private collection i have decided to make public.
Long time ago, I used to be administrator of the biggest czech BG/Arena server. During that time, i often questioned myself - where to put all those f*cking vendors? 
Well, this script is the final solution of that question. 
It combines multiple vendors into single one, which, as far as i know, was impossible on trinity, unless the included vendors were nearby.
There is only one problem - all of your generic vendors have to have simple script attached (which is same for all of them) and gossip flag added. It could be done with single sql query, so it shouldnt be much of problem.
Lets get started:
- Create npc with npcflag 129 and set scriptname to vendor_multi
- Set all generic vendors flag to 129 and scriptname to vendor_general
- Know how to add creature scripts to ScriptLoaderu
Sql - import to world db, ill explain later
Code:
CREATE TABLE `_customvendors` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`vendor` int(11) DEFAULT NULL,
`group` int(11) DEFAULT NULL,
`next` int(11) DEFAULT NULL,
`desc` text,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=59 DEFAULT CHARSET=latin1;
C++ - lines with + are to be added (without +
)
Player.h
Code:
class Player : public Unit, public GridObject<Player>
{
friend class WorldSession;
friend void Item::AddToUpdateQueueOf(Player* player);
friend void Item::RemoveFromUpdateQueueOf(Player* player);
public:
+ int currentVendorEntry;
Player.cpp
Code:
Player::Player(WorldSession* session): Unit(true), m_achievementMgr(this), m_reputationMgr(this)
{
m_speakTime = 0;
m_speakCount = 0;
+ currentVendorEntry = -1;
now edit the function bool Player::BuyItemFromVendorSlot(uint64 vendorguid, uint32 vendorslot, uint32 item, uint8 count, uint8 bag, uint8 slot) like this. Dont have the original code, sorry 
Code:
bool Player::BuyItemFromVendorSlot(uint64 vendorguid, uint32 vendorslot, uint32 item, uint8 count, uint8 bag, uint8 slot)
{
// cheating attempt
if (count < 1) count = 1;
// cheating attempt
if (slot > MAX_BAG_SIZE && slot !=NULL_SLOT)
return false;
if (!isAlive())
return false;
ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(item);
if (!pProto)
{
SendBuyError(BUY_ERR_CANT_FIND_ITEM, NULL, item, 0);
return false;
}
Creature* creature = GetNPCIfCanInteractWith(vendorguid, UNIT_NPC_FLAG_VENDOR);
if (!creature)
{
sLog->outDebug(LOG_FILTER_NETWORKIO, "WORLD: BuyItemFromVendor - Unit (GUID: %u) not found or you can't interact with him.", uint32(GUID_LOPART(vendorguid)));
SendBuyError(BUY_ERR_DISTANCE_TOO_FAR, NULL, item, 0);
return false;
}
+ VendorItemData const* vItems;
+ if(currentVendorEntry != -1)
+ vItems = sObjectMgr->GetNpcVendorItemList(currentVendorEntry); // creature->GetVendorItems();
+ else
+ vItems = creature->GetVendorItems();
if (!vItems || vItems->Empty())
{
and in the same function edit
Code:
if (crItem->maxcount != 0)
{
if (creature->GetVendorItemCurrentCount(crItem) < pProto->BuyCount * count)
{
SendBuyError(BUY_ERR_ITEM_ALREADY_SOLD, creature, item, 0);
return false;
}
}
like this
Code:
if (crItem->maxcount != 0 && currentVendorEntry == -1)
{
if (creature->GetVendorItemCurrentCount(crItem) < pProto->BuyCount * count)
{
SendBuyError(BUY_ERR_ITEM_ALREADY_SOLD, creature, item, 0);
return false;
}
}
Worldsession.h
Code:
void SendNameQueryOpcode(uint64 guid);
void SendTrainerList(uint64 guid);
void SendTrainerList(uint64 guid, const std::string& strTitle);
void SendListInventory(uint64 guid);
+ void SendListInventory(uint64 vendorGuid, int entry);
ItemHandler.cpp
after function void WorldSession::SendListInventory(uint64 vendorGuid) add
Code:
void WorldSession::SendListInventory(uint64 vendorGuid, int entry)
{
sLog->outDebug(LOG_FILTER_NETWORKIO, "WORLD: Sent SMSG_LIST_INVENTORY");
Creature* vendor = GetPlayer()->GetNPCIfCanInteractWith(vendorGuid, UNIT_NPC_FLAG_VENDOR);
if (!vendor)
{
sLog->outDebug(LOG_FILTER_NETWORKIO, "WORLD: SendListInventory - Unit (GUID: %u) not found or you can not interact with him.", uint32(GUID_LOPART(vendorGuid)));
_player->SendSellError(SELL_ERR_CANT_FIND_VENDOR, NULL, 0, 0);
return;
}
// remove fake death
if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
// Stop the npc if moving
if (vendor->HasUnitState(UNIT_STATE_MOVING))
vendor->StopMoving();
VendorItemData const* items = sObjectMgr->GetNpcVendorItemList(entry);//vendor->GetVendorItems();
if (!items)
{
WorldPacket data(SMSG_LIST_INVENTORY, 8 + 1 + 1);
data << uint64(vendorGuid);
data << uint8(0); // count == 0, next will be error code
data << uint8(0); // "Vendor has no inventory"
SendPacket(&data);
return;
}
uint8 itemCount = items->GetItemCount();
uint8 count = 0;
WorldPacket data(SMSG_LIST_INVENTORY, 8 + 1 + itemCount * 8 * 4);
data << uint64(vendorGuid);
size_t countPos = data.wpos();
data << uint8(count);
float discountMod = _player->GetReputationPriceDiscount(vendor);
for (uint8 slot = 0; slot < itemCount; ++slot)
{
if (VendorItem const* item = items->GetItem(slot))
{
if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(item->item))
{
if (!(itemTemplate->AllowableClass & _player->getClassMask()) && itemTemplate->Bonding == BIND_WHEN_PICKED_UP && !_player->isGameMaster())
continue;
// Only display items in vendor lists for the team the
// player is on. If GM on, display all items.
if (!_player->isGameMaster() && ((itemTemplate->Flags2 & ITEM_FLAGS_EXTRA_HORDE_ONLY && _player->GetTeam() == ALLIANCE) || (itemTemplate->Flags2 == ITEM_FLAGS_EXTRA_ALLIANCE_ONLY && _player->GetTeam() == HORDE)))
continue;
// Items sold out are not displayed in list
uint32 leftInStock = !item->maxcount ? 0xFFFFFFFF : vendor->GetVendorItemCurrentCount(item);
if (!_player->isGameMaster() && !leftInStock)
continue;
++count;
// reputation discount
int32 price = item->IsGoldRequired(itemTemplate) ? uint32(floor(itemTemplate->BuyPrice * discountMod)) : 0;
data << uint32(slot + 1); // client expects counting to start at 1
data << uint32(item->item);
data << uint32(itemTemplate->DisplayInfoID);
data << int32(leftInStock);
data << uint32(price);
data << uint32(itemTemplate->MaxDurability);
data << uint32(itemTemplate->BuyCount);
data << uint32(item->ExtendedCost);
}
}
}
if (count == 0)
{
data << uint8(0);
SendPacket(&data);
return;
}
data.put<uint8>(countPos, count);
SendPacket(&data);
}
ScriptMgr.cpp
Code:
#include "ScriptPCH.h"
#include "ScriptMgr.h"
#include "Config.h"
#include "DatabaseEnv.h"
#include "DBCStores.h"
#include "ObjectMgr.h"
#include "OutdoorPvPMgr.h"
#include "ScriptLoader.h"
#include "ScriptSystem.h"
#include "Transport.h"
#include "Vehicle.h"
+ #include "CustomVendor.h"
Code:
sLog->outString(">> Loaded %u C++ scripts in %u ms", GetScriptCount(), GetMSTimeDiffToNow(oldMSTime));
sLog->outString();
+ oldMSTime = getMSTime();
+ sLog->outString("Loading custom vendors");
+ sLog->outString(">> Loaded %u custom vendor catageory entries in %u ms", CustomVendorMgr.LoadVendors(), GetMSTimeDiffToNow(oldMSTime));
+ sLog->outString();
Now, add these scripts to core (game project)
CustomVendor.h
Code:
#ifndef CUSTOMVENDOR_H
#define CUSTOMVENDOR_H
extern WorldDatabaseWorkerPool WorldDatabase;
class VendorEntry
{
public:
int id, vendor, group, next;
std::string desc;
VendorEntry(int, int, int, int, std::string);
};
typedef std::list<VendorEntry *> VendorEntryList;
class CustomVendor
{
public:
VendorEntryList vendorEntryList;
CustomVendor(void);
~CustomVendor(void);
int LoadVendors(void);
VendorEntryList* GetItemsForEntry(int, int);
VendorEntryList* GetBaseItemsForEntry(int);
int GetGroup(int);
int GetNext(int, int);
VendorEntry* GetParent(int);
};
extern CustomVendor CustomVendorMgr;
#endif
And CustomVendor.cpp
Code:
#include "CustomVendor.h"
#include "gamePCH.h"
#include <stdio.h>
#include <string>
#include <list>
CustomVendor CustomVendorMgr;
CustomVendor::CustomVendor(void)
{
}
CustomVendor::~CustomVendor(void)
{
}
int CustomVendor::LoadVendors(void)
{
vendorEntryList.clear();
int i = 0;
QueryResult result = WorldDatabase.PQuery("SELECT `id`, `vendor`, `group`, `next`, `desc` FROM _customvendors");
// 0 1 2 3 4
if (result)
{
do
{
Field *fields = result->Fetch();
int id = fields[0].GetInt32();
int vendor = fields[1].GetInt32();
int group = fields[2].GetInt32();
int next = fields[3].GetInt32();
std::string desc = fields[4].GetString();
vendorEntryList.push_back(new VendorEntry(id, vendor, group, next, desc));
i++;
}
while (result->NextRow());
}
return i;
}
VendorEntryList* CustomVendor::GetItemsForEntry(int entry, int id)
{
VendorEntryList *result = new VendorEntryList();
VendorEntryList::iterator i;
int group = GetGroup(id);
for (i = vendorEntryList.begin(); i != vendorEntryList.end(); ++i)
{
VendorEntry *vendorEntry = *i;
if(vendorEntry->group == group && vendorEntry->vendor == entry)
result->push_back(vendorEntry);
}
return result;
}
VendorEntryList* CustomVendor::GetBaseItemsForEntry(int entry)
{
VendorEntryList *result = new VendorEntryList();
VendorEntryList::iterator i;
for (i = vendorEntryList.begin(); i != vendorEntryList.end(); ++i)
{
VendorEntry *vendorEntry = *i;
if(vendorEntry->group == 0 && vendorEntry->vendor == entry)
result->push_back(vendorEntry);
}
return result;
}
int CustomVendor::GetGroup(int id)
{
VendorEntryList::iterator i;
for (i = vendorEntryList.begin(); i != vendorEntryList.end(); ++i)
{
VendorEntry *vendorEntry = *i;
if(vendorEntry->id == id)
return vendorEntry->group;
}
return -1;
}
int CustomVendor::GetNext(int entry, int id)
{
VendorEntryList::iterator i;
for (i = vendorEntryList.begin(); i != vendorEntryList.end(); ++i)
{
VendorEntry *vendorEntry = *i;
if(vendorEntry->id == id && vendorEntry->vendor == entry)
return vendorEntry->next;
}
return -1;
}
VendorEntry* CustomVendor::GetParent(int id)
{
VendorEntryList::iterator i;
for (i = vendorEntryList.begin(); i != vendorEntryList.end(); ++i)
{
VendorEntry *vendorEntry = *i;
if(vendorEntry->next == id)
return vendorEntry;
}
return NULL;
}
VendorEntry::VendorEntry(int id, int vendor, int group, int next, std::string desc)
{
this->id = id;
this->vendor = vendor;
this->group = group;
this->next = next;
this->desc = desc;
}
And finaly, npc scripts:
vendor_multi.cpp
Code:
#include "ScriptPCH.h"
#include <cstring>
#include "../CustomVendor.h" // upravte si cestu
class vendor_multi : public CreatureScript
{
public:
vendor_multi()
: CreatureScript("vendor_multi")
{
}
bool OnGossipHello(Player* player, Creature* creature)
{
IterateCategory(player, creature);
return true;
}
void IterateCategory(Player* player, Creature* creature, int category = 0)
{
player->PlayerTalkClass->ClearMenus();
player->currentVendorEntry = -1;
VendorEntry *current = current = CustomVendorMgr.GetParent(category);
VendorEntryList *result;
if(category == 0)
result = CustomVendorMgr.GetBaseItemsForEntry(creature->GetCreatureInfo()->Entry);
else
result = CustomVendorMgr.GetItemsForEntry(creature->GetCreatureInfo()->Entry, category);
VendorEntryList::iterator i;
int x = 0;
for (i = result->begin(); i != result->end(); ++i)
{
VendorEntry *vendorEntry = *i;
int icon = GOSSIP_ICON_CHAT;
if(CustomVendorMgr.GetNext(creature->GetCreatureInfo()->Entry, vendorEntry->next) < 0)
icon = GOSSIP_ICON_MONEY_BAG;
player->ADD_GOSSIP_ITEM(icon, vendorEntry->desc, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+2+vendorEntry->next);
x++;
if(x == 5)
{
player->PlayerTalkClass->SendGossipMenu(907, creature->GetGUID());
x = 0;
}
}
if(current)
{
if(current->group != 0)
player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "<< Zpet", GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+2+current->id);
else
player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "<< Zpet", GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+1);
x++;
}
if(x != 0)
player->PlayerTalkClass->SendGossipMenu(907, creature->GetGUID());
}
bool OnGossipSelect(Player* player, Creature* creature, uint32 /*uiSender*/, uint32 uiAction)
{
if (uiAction == GOSSIP_ACTION_INFO_DEF+1)
{
IterateCategory(player, creature);
}
else if (uiAction > GOSSIP_ACTION_INFO_DEF+1)
{
int id = uiAction-GOSSIP_ACTION_INFO_DEF-2;
int next = CustomVendorMgr.GetNext(creature->GetCreatureInfo()->Entry, id);
if(next < 0)
{
next = (-1) * next;
player->currentVendorEntry = next;
player->GetSession()->SendListInventory(creature->GetGUID(), next);
}
else
IterateCategory(player, creature, id);
}
return true;
}
};
void AddSC_vendor_multi()
{
new vendor_multi();
}
a vendor_general.cpp
Code:
#include "ScriptPCH.h"
#include <cstring>
class vendor_general : public CreatureScript
{
public:
vendor_general()
: CreatureScript("vendor_general")
{
}
bool OnGossipHello(Player* player, Creature* creature)
{
player->currentVendorEntry = -1;
player->GetSession()->SendListInventory(creature->GetGUID());
return true;
}
};
void AddSC_vendor_general()
{
new vendor_general();
}
And now ill briefly explain db part; its a bit impractical, but i was layz to remade it once i found how hard it is to edit it :P
`vendor` - entry npc that should diplay this record
`group` - all records that should be shown ingame on the same page should have same group
`next` - next id (ID from this table) of record, that group that we eant to show
`desc` - text, který se zobrazí
Vendors used for npc in video
Code:
Entry Name Subname
17 One handed Axe 1 Weapon One hand
18 Two handed Axe 1 Weapon Two-Hand
19 Bow 1 Weapon Ranged
20 Gun 1 Weapon Ranged
21 One handed Mace 1 Weapon One hand
22 One handed Mace 1 Weapon Main hand
23 Two handed Mace 1 Weapon Two-Hand
24 Polearm 1 Weapon Two-Hand
25 One handed Sword 1 Weapon One hand
26 One handed Sword 1 Weapon Main hand
27 Two handed Sword 1 Weapon Two-Hand
28 Staff 1 Weapon Two-Hand
29 Fist Weapon 1 Weapon Main hand
30 Fist Weapon 1 Weapon Off hand
31 Dagger 1 Weapon One hand
32 Dagger 1 Weapon Main hand
33 Thrown 1 Weapon Thrown
34 Crossbow 1 Weapon Ranged
35 Wand 1 Weapon Ranged right
and i filled the custom table like this
Code:
id vendor group next desc
58 215 1000 -35 Buy: wand
57 215 1000 -33 Buy: thrown
56 215 1000 -34 Buy: crossbow
55 215 1000 -20 Buy: gun
54 215 1000 -19 Buy: bow
53 215 3 58 Wands
52 215 3 57 Thrown
51 215 3 56 Crossbows
50 215 3 55 Guns
49 215 3 54 Bows
48 215 1000 -24 Buy: polearm
47 215 1000 -28 Buy: Staff
46 215 1000 -27 Buy: 2h sword
45 215 1000 -23 Buy: 2h mace
44 215 1000 -18 Buy: 2H axe
43 215 2 48 Polearms
42 215 2 47 Staves
36 215 2 46 Swords
35 215 2 45 Maces
34 215 2 44 Axes
33 215 1000 -30 Buy: Off hand fist weapon
32 215 1000 -29 Buy: Main hand fist weapon
31 215 1000 -25 Buy: One hand sword
30 215 1000 -26 Buy: Main hand sword
29 215 1000 -21 Buy: One hand mace
28 215 1000 -22 Buy: Main hand mace
27 215 14 33 Off hand Fist weapon
26 215 14 32 Main hand Fist weapon
25 215 13 31 One hand Sword
24 215 13 30 Main hand Sword
23 215 12 29 One hand Mace
22 215 12 28 Main hand Mace
21 215 1000 -17 Buy: One hand Axe
41 215 1000 -31 Buy: One hand Dagger
40 215 1000 -32 Buy: Main hand Dagger
39 215 15 41 One hand Dagger
38 215 15 40 Main hand Dagger
37 215 1 38 Daggers
19 215 1 26 Fist weapons
18 215 1 24 Swords
17 215 1 22 Maces
16 215 1 21 Axes
15 215 0 49 Ranged
14 215 0 34 Two handed
13 215 0 16 One handed
Records shown on first page in npc should always have group 0!
To make it easier readable, i use group 1000 for every record that displays vendor content. When next is set to negative value, it means that vendor with entry equal to next * (-1) should be opened.
Lets look at this example. Lets say i want to buy Dagger.
So, the first record is
Code:
13 215 0 16 One handed
group 0, next is id 16 there are these records with the same group
Code:
37 215 1 38 Daggers
19 215 1 26 Fist weapons
18 215 1 24 Swords
17 215 1 22 Maces
16 215 1 21 Axes
because of that, when i click on One handed in game, it shows a list with Daggers, Fist weapons, Swords, Axes - you would see tha same list if id of next record is set to 18 (Swords), because they all share the same group (1)
Clicking on daggers produces following list
Code:
39 215 15 41 One hand Dagger
38 215 15 40 Main hand Dagger
(because record with id 38 has got group 15, it would work as well if next is set to 39) - i selected main hand, record with id 40 is shown
Code:
40 215 1000 -32 Buy: Main hand Dagger
this record got its next value equal to (-32) - thats why no list is shown, but vendor with entry 32 is shown.
Hopefuly its understandable.