Browse Source

* Implemented search store info system (aka. vending and buying store search) together with related items.
- Requires 2010-08-03aRagexeRE or later and can be disabled in 'conf/battle/feature.conf'.

git-svn-id: https://svn.code.sf.net/p/rathena/svn/trunk@14732 54d463be-8e91-2dee-dedb-b68131a5f0ec

ai4rei 14 years ago
parent
commit
6b74ef9b50

+ 3 - 0
Changelog-Trunk.txt

@@ -1,5 +1,8 @@
 Date	Added
 
+2011/03/06
+	* Implemented search store info system (aka. vending and buying store search) together with related items. [Ai4rei]
+	- Requires 2010-08-03aRagexeRE or later and can be disabled in 'conf/battle/feature.conf'.
 2011/03/05
 	* Fixed possible crash in script_reportdata, when a script string becomes NULL for whatever reason. [Ai4rei]
 2011/03/04

+ 2 - 0
conf/Changelog.txt

@@ -1,5 +1,7 @@
 Date	Added
 
+2011/03/06
+	* Rev. 14732 Added search store info related settings. [Ai4rei]
 2011/02/23
 	* Rev. 14724 Made autotrade error message store type-neutral, as it is used for buying stores now as well. [Ai4rei]
 2011/02/19

+ 5 - 1
conf/battle/feature.conf

@@ -20,5 +20,9 @@
 //--------------------------------------------------------------
 
 // Buying store (Note 1)
-// Requires: 2010-04-20aRagexeRE or later
+// Requires: 2010-04-27aRagexeRE or later
 feature.buying_store: on
+
+// Search stores (Note 1)
+// Requires: 2010-08-03aRagexeRE or later
+feature.search_stores: on

+ 7 - 0
conf/battle/misc.conf

@@ -121,3 +121,10 @@ auction_feeperhour: 12000
 
 // Auction maximum sell price
 auction_maximumprice: 500000000
+
+// Minimum delay between each store search query in seconds.
+searchstore_querydelay: 10
+
+// Maximum amount of results a store search query may yield, before
+// it is canceled.
+searchstore_maxresults: 30

+ 3 - 0
db/Changelog.txt

@@ -9,6 +9,9 @@
 	13005 Angelic Wing Dagger:	NEED INFO.
 =======================
 
+2011/03/06
+	* Rev. 14732 Added Universal Catalog Silver, Gold and Bronze and their respective boxes. [Ai4rei]
+	- Updated packet info related to search store info.
 2011/02/19
 	* Rev. 14713 Database updates required by buying store system implementation. [Ai4rei]
 	- Added database of items, that can be sold to a buying store (item_buyingstore.txt).

+ 9 - 0
db/item_db.txt

@@ -4830,6 +4830,9 @@
 //12556,Sleipnir_Piece_Box,
 //12557,Mjolnir_Piece_Box,
 //12558,Megingiorde_Piece_Box,
+12580,Universal_Catalog_Silver,Universal Catalog Silver,2,200,,10,,,,,0xFFFFFFFF,7,2,,,,,,{ searchstores 10,0; },{},{}
+12581,Universal_Catalog_Gold,Universal Catalog Gold,2,0,,10,,,,,0xFFFFFFFF,7,2,,,,,,{ searchstores 10,1; },{},{}
+12591,Universal_Catalog_Bronze,Universal Catalog Bronze,2,0,,10,,,,,0xFFFFFFFF,7,2,,,,,,{ searchstores 10,1; },{},{}
 12701,Old_Blue_Box_F,Old Blue Box,2,,,200,,,,,0xFFFFFFFF,7,2,,,,,,{},{},{}
 12702,Old_Bleu_Box,Old Navy Box,2,0,,200,,,,,0xFFFFFFFF,7,2,,,,,,{ getitem groupranditem(IG_BleuBox),1; getitem groupranditem(IG_BleuBox),1; },{},{}
 12703,Holy_Egg_2,Holy Egg,11,0,,50,,,,,0xFFFFFFFF,7,2,,,,,,{},{},{}
@@ -6183,6 +6186,12 @@
 //16588,Thoughtful_Hat_Box
 //16589,Thoughtful_Hat_Box
 //16590,Thoughtful_Hat_Box
+16677,Universal_Catalog_Gold_Box10,Universal Catalog Gold 10 Box,2,0,,10,,,,,0xFFFFFFFF,7,2,,,,,,{ getitem 12581,10; },{},{}
+16678,Universal_Catalog_Gold_Box50,Universal Catalog Gold 50 Box,2,0,,10,,,,,0xFFFFFFFF,7,2,,,,,,{ getitem 12581,50; },{},{}
+16679,Universal_Catalog_Gold_Box10,Universal Catalog Gold 10 Box,2,0,,10,,,,,0xFFFFFFFF,7,2,,,,,,{ getitem 12581,10; },{},{}
+16680,Universal_Catalog_Gold_Box50,Universal Catalog Gold 50 Box,2,0,,10,,,,,0xFFFFFFFF,7,2,,,,,,{ getitem 12581,50; },{},{}
+16776,Universal_Catalog_Gold_Box10,Universal Catalog Gold 10 Box,2,0,,10,,,,,0xFFFFFFFF,7,2,,,,,,{ getitem 12581,10; },{},{}
+16777,Universal_Catalog_Gold_Box50,Universal Catalog Gold 50 Box,2,0,,10,,,,,0xFFFFFFFF,7,2,,,,,,{ getitem 12581,50; },{},{}
 //18000,Cannon_Ball
 //18001,Holy_Cannon_Ball
 //18002,Dark_Cannon_Ball

+ 9 - 9
db/packet_db.txt

@@ -1541,17 +1541,17 @@ packet_ver: 25
 //2010-06-01aRagexeRE
 //0x0825,-1
 //0x0826,4
-//0x0835,-1
-//0x0836,-1
-//0x0837,3
+0x0835,-1,searchstoreinfo,2:4:5:9:13:14:15
+0x0836,-1
+0x0837,3
 //0x0838,3
 
 //2010-06-08aRagexeRE
-//0x0838,2
-//0x083A,4 	// Search Stalls Feature
-//0x083B,2
-//0x083C,12
-//0x083D,6
+0x0838,2,searchstoreinfonextpage,0
+0x083A,4 	// Search Stalls Feature
+0x083B,2,closesearchstoreinfo,0
+0x083C,12,searchstoreinfolistitemclick,2:6:10
+0x083D,6
 
 //2010-06-15aRagexeRE
 //0x083E,26
@@ -1566,7 +1566,7 @@ packet_ver: 25
 //0x07F3,6
 
 //2010-07-01aRagexeRE
-//0x083A,5 	// Search Stalls Feature
+0x083A,5 	// Search Stalls Feature
 
 //2010-07-13aRagexeRE
 //0x0827,6

+ 23 - 1
doc/script_commands.txt

@@ -4,7 +4,7 @@
 //= A reference manual for the eAthena scripting language.
 //= Commands are sorted depending on their functionality.
 //===== Version ===========================================
-//= 3.36.20110219
+//= 3.37.20110306
 //=========================================================
 //= 1.0 - First release, filled will as much info as I could
 //=       remember or figure out, most likely there are errors,
@@ -157,6 +157,8 @@
 //=       Removed bug warning from 'deletearray'. [Ai4rei]
 //= 3.36.20110219
 //=       Added 'buyingstore' command. [Ai4rei]
+//= 3.37.20110306
+//=       Added 'searchstores' command. [Ai4rei]
 //=========================================================
 
 This document is a reference manual for all the scripting commands and functions 
@@ -4438,6 +4440,26 @@ Example:
 	// Gives the player oppurtunity to buy 4 different kinds of items.
 	buyingstore 4;
 
+---------------------------------------
+
+*searchstores <uses>,<effect>;
+
+Invokes the store search window, which allows to search for both vending
+and buying stores. Parameter uses indicates, how many searches can be
+started, before the window has to be reopened. Effect value affects,
+what happens, when a result item is double-clicked and can be one of the
+following:
+
+	0 = Shows the store's position on the mini-map and highlights the
+		shop sign with yellow color, when the store is on same map
+		as the invoking player.
+	1 = Directly opens the shop, regardless of distance.
+
+Example:
+
+	// Item Universal_Catalog_Gold (10 uses, effect: open shop)
+	searchstores 10,1;
+
 ---------------------------------------
 //
 4,1.- End of item-related commands

+ 2 - 2
src/map/Makefile.in

@@ -18,7 +18,7 @@ MAP_OBJ = map.o chrif.o clif.o pc.o status.o npc.o \
 	storage.o skill.o atcommand.o battle.o battleground.o \
 	intif.o trade.o party.o vending.o guild.o pet.o \
 	log.o mail.o date.o unit.o homunculus.o mercenary.o quest.o instance.o \
-	buyingstore.o
+	buyingstore.o searchstore.o
 MAP_TXT_OBJ = $(MAP_OBJ:%=obj_txt/%) \
 	obj_txt/mapreg_txt.o
 MAP_SQL_OBJ = $(MAP_OBJ:%=obj_sql/%) \
@@ -28,7 +28,7 @@ MAP_H = map.h chrif.h clif.h pc.h status.h npc.h \
 	storage.h skill.h atcommand.h battle.h battleground.h \
 	intif.h trade.h party.h vending.h guild.h pet.h \
 	log.h mail.h date.h unit.h homunculus.h mercenary.h quest.h instance.h mapreg.h \
-	buyingstore.h
+	buyingstore.h searchstore.h
 
 HAVE_MYSQL=@HAVE_MYSQL@
 ifeq ($(HAVE_MYSQL),yes)

+ 3 - 0
src/map/battle.c

@@ -4008,6 +4008,9 @@ static const struct _battle_data {
 	{ "client_sort_storage",                &battle_config.client_sort_storage,             0,      0,      1,              },
 	{ "gm_check_minlevel",                  &battle_config.gm_check_minlevel,               60,     0,      100,            },
 	{ "feature.buying_store",               &battle_config.feature_buying_store,            1,      0,      1,              },
+	{ "feature.search_stores",              &battle_config.feature_search_stores,           1,      0,      1,              },
+	{ "searchstore_querydelay",             &battle_config.searchstore_querydelay,         10,      0,      INT_MAX,        },
+	{ "searchstore_maxresults",             &battle_config.searchstore_maxresults,         30,      1,      INT_MAX,        },
 // BattleGround Settings
 	{ "bg_update_interval",                 &battle_config.bg_update_interval,              1000,   100,    INT_MAX,        },
 	{ "bg_short_attack_damage_rate",        &battle_config.bg_short_damage_rate,            80,     0,      INT_MAX,        },

+ 3 - 0
src/map/battle.h

@@ -482,6 +482,9 @@ extern struct Battle_Config
 	int client_sort_storage;
 	int gm_check_minlevel;  // min GM level for /check
 	int feature_buying_store;
+	int feature_search_stores;
+	int searchstore_querydelay;
+	int searchstore_maxresults;
 
 	// [BattleGround Settings]
 	int bg_update_interval;

+ 72 - 5
src/map/buyingstore.c

@@ -34,6 +34,7 @@ enum e_buyingstore_failure
 
 
 static unsigned int buyingstore_nextid = 0;
+static short buyingstore_blankslots[MAX_SLOTS] = { 0 };  // used when checking whether or not an item's card slots are blank
 
 
 /// Returns unique buying store id
@@ -217,7 +218,7 @@ void buyingstore_open(struct map_session_data* sd, int account_id)
 		return;
 	}
 
-	if( sd->bl.m != pl_sd->bl.m || !check_distance_bl(&sd->bl, &pl_sd->bl, AREA_SIZE) )
+	if( !searchstore_queryremote(sd, account_id) && ( sd->bl.m != pl_sd->bl.m || !check_distance_bl(&sd->bl, &pl_sd->bl, AREA_SIZE) ) )
 	{// out of view range
 		return;
 	}
@@ -229,7 +230,6 @@ void buyingstore_open(struct map_session_data* sd, int account_id)
 
 void buyingstore_trade(struct map_session_data* sd, int account_id, unsigned int buyer_id, const uint8* itemlist, unsigned int count)
 {
-	short blankslots[MAX_SLOTS];  // used when checking whether or not an item's card slots are blank
 	int zeny = 0;
 	unsigned int i, weight, listidx, k;
 	struct map_session_data* pl_sd;
@@ -258,18 +258,19 @@ void buyingstore_trade(struct map_session_data* sd, int account_id, unsigned int
 		return;
 	}
 
-	if( sd->bl.m != pl_sd->bl.m || !check_distance_bl(&sd->bl, &pl_sd->bl, AREA_SIZE) )
+	if( !searchstore_queryremote(sd, account_id) && ( sd->bl.m != pl_sd->bl.m || !check_distance_bl(&sd->bl, &pl_sd->bl, AREA_SIZE) ) )
 	{// out of view range
 		clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0);
 		return;
 	}
 
+	searchstore_clearremote(sd);
+
 	if( pl_sd->status.zeny < pl_sd->buyingstore.zenylimit )
 	{// buyer lost zeny in the mean time? fix the limit
 		pl_sd->buyingstore.zenylimit = pl_sd->status.zeny;
 	}
 	weight = pl_sd->weight;
-	memset(blankslots, 0, sizeof(blankslots));
 
 	// check item list
 	for( i = 0; i < count; i++ )
@@ -299,7 +300,7 @@ void buyingstore_trade(struct map_session_data* sd, int account_id, unsigned int
 			return;
 		}
 
-		if( sd->status.inventory[index].expire_time || !itemdb_cantrade(&sd->status.inventory[index], pc_isGM(sd), pc_isGM(pl_sd)) || memcmp(sd->status.inventory[index].card, blankslots, sizeof(blankslots)) )
+		if( sd->status.inventory[index].expire_time || !itemdb_cantrade(&sd->status.inventory[index], pc_isGM(sd), pc_isGM(pl_sd)) || memcmp(sd->status.inventory[index].card, buyingstore_blankslots, sizeof(buyingstore_blankslots)) )
 		{// non-tradable item
 			clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid);
 			return;
@@ -401,3 +402,69 @@ void buyingstore_trade(struct map_session_data* sd, int account_id, unsigned int
 		map_quit(pl_sd);
 	}
 }
+
+
+/// Checks if an item is being bought in given player's buying store.
+bool buyingstore_search(struct map_session_data* sd, unsigned short nameid)
+{
+	unsigned int i;
+
+	if( !sd->state.buyingstore )
+	{// not buying
+		return false;
+	}
+
+	ARR_FIND( 0, sd->buyingstore.slots, i, sd->buyingstore.items[i].nameid == nameid && sd->buyingstore.items[i].amount );
+	if( i == sd->buyingstore.slots )
+	{// not found
+		return false;
+	}
+
+	return true;
+}
+
+
+/// Searches for all items in a buyingstore, that match given ids, price and possible cards.
+/// @return Whether or not the search should be continued.
+bool buyingstore_searchall(struct map_session_data* sd, const struct s_search_store_search* s)
+{
+	unsigned int i, idx;
+	struct s_buyingstore_item* it;
+
+	if( !sd->state.buyingstore )
+	{// not buying
+		return true;
+	}
+
+	for( idx = 0; idx < s->item_count; idx++ )
+	{
+		ARR_FIND( 0, sd->buyingstore.slots, i, sd->buyingstore.items[i].nameid == s->itemlist[idx] && sd->buyingstore.items[i].amount );
+		if( i == sd->buyingstore.slots )
+		{// not found
+			continue;
+		}
+		it = &sd->buyingstore.items[i];
+
+		if( s->min_price && s->min_price > (unsigned int)it->price )
+		{// too low price
+			continue;
+		}
+
+		if( s->max_price && s->max_price < (unsigned int)it->price )
+		{// too high price
+			continue;
+		}
+
+		if( s->card_count )
+		{// ignore cards, as there cannot be any
+			;
+		}
+
+		if( !searchstore_result(s->search_sd, sd->buyer_id, sd->status.account_id, sd->message, it->nameid, it->amount, it->price, buyingstore_blankslots, 0) )
+		{// result set full
+			return false;
+		}
+	}
+
+	return true;
+}

+ 4 - 0
src/map/buyingstore.h

@@ -4,6 +4,8 @@
 #ifndef _BUYINGSTORE_H_
 #define _BUYINGSTORE_H_
 
+struct s_search_store_search;
+
 #define MAX_BUYINGSTORE_SLOTS 5
 
 struct s_buyingstore_item
@@ -25,5 +27,7 @@ void buyingstore_create(struct map_session_data* sd, int zenylimit, unsigned cha
 void buyingstore_close(struct map_session_data* sd);
 void buyingstore_open(struct map_session_data* sd, int account_id);
 void buyingstore_trade(struct map_session_data* sd, int account_id, unsigned int buyer_id, const uint8* itemlist, unsigned int count);
+bool buyingstore_search(struct map_session_data* sd, unsigned short nameid);
+bool buyingstore_searchall(struct map_session_data* sd, const struct s_search_store_search* s);
 
 #endif  // _BUYINGSTORE_H_

+ 198 - 1
src/map/clif.c

@@ -14323,6 +14323,198 @@ void clif_buyingstore_trade_failed_seller(struct map_session_data* sd, short res
 }
 
 
+/// Search Store Info System
+///
+
+/// Request to search for stores (CZ_SEARCH_STORE_INFO)
+/// 0835 <packet len>.W <type>.B <max price>.L <min price>.L <name id count>.B <card count>.B { <name id>.W }* { <card>.W }*
+/// type:
+///     0 = Vending
+///     1 = Buying Store
+///
+/// @note   The client determines the item ids by specifying a name and optionally,
+///         amount of card slots. If the client does not know about the item it
+///         cannot be searched.
+static void clif_parse_SearchStoreInfo(int fd, struct map_session_data* sd)
+{
+	const unsigned int blocksize = 2;
+	const uint8* itemlist;
+	const uint8* cardlist;
+	unsigned char type;
+	unsigned int min_price, max_price, packet_len, count, item_count, card_count;
+	struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)];
+
+	packet_len = RFIFOW(fd,info->pos[0]);
+
+	if( packet_len < 15 )
+	{// minimum packet length
+		ShowError("clif_parse_SearchStoreInfo: Malformed packet (expected length=%u, length=%u, account_id=%d).\n", 15, packet_len, sd->bl.id);
+		return;
+	}
+
+	type       = RFIFOB(fd,info->pos[1]);
+	max_price  = RFIFOL(fd,info->pos[2]);
+	min_price  = RFIFOL(fd,info->pos[3]);
+	item_count = RFIFOB(fd,info->pos[4]);
+	card_count = RFIFOB(fd,info->pos[5]);
+	itemlist   = RFIFOP(fd,info->pos[6]);
+	cardlist   = RFIFOP(fd,info->pos[6]+blocksize*item_count);
+
+	// check, if there is enough data for the claimed count of items
+	packet_len-= info->pos[6];
+
+	if( packet_len%blocksize )
+	{
+		ShowError("clif_parse_SearchStoreInfo: Unexpected item list size %u (account_id=%d, block size=%u)\n", packet_len, sd->bl.id, blocksize);
+		return;
+	}
+	count = packet_len/blocksize;
+
+	if( count < item_count+card_count )
+	{
+		ShowError("clif_parse_SearchStoreInfo: Malformed packet (expected count=%u, count=%u, account_id=%d).\n", item_count+card_count, count, sd->bl.id);
+		return;
+	}
+
+	searchstore_query(sd, type, min_price, max_price, (const unsigned short*)itemlist, item_count, (const unsigned short*)cardlist, card_count);
+}
+
+
+/// Results for a store search request (ZC_SEARCH_STORE_INFO_ACK)
+/// 0836 <packet len>.W <is first page>.B <is next page>.B <remaining uses>.B { <store id>.L <account id>.L <shop name>.80B <nameid>.W <item type>.B <price>.L <amount>.W <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W }*
+/// is first page:
+///     0 = appends to existing results
+///     1 = clears previous results before displaying this result set
+/// is next page:
+///     0 = no "next" label
+///     1 = "next" label to retrieve more results
+void clif_search_store_info_ack(struct map_session_data* sd)
+{
+	const unsigned int blocksize = MESSAGE_SIZE+26;
+	int fd = sd->fd;
+	unsigned int i, start, end;
+
+	start = sd->searchstore.pages*SEARCHSTORE_RESULTS_PER_PAGE;
+	end   = min(sd->searchstore.count, start+SEARCHSTORE_RESULTS_PER_PAGE);
+
+	WFIFOHEAD(fd,7+(end-start)*blocksize);
+	WFIFOW(fd,0) = 0x836;
+	WFIFOW(fd,2) = 7+(end-start)*blocksize;
+	WFIFOB(fd,4) = !sd->searchstore.pages;
+	WFIFOB(fd,5) = searchstore_querynext(sd);
+	WFIFOB(fd,6) = (unsigned char)min(sd->searchstore.uses, UCHAR_MAX);
+
+	for( i = start; i < end; i++ )
+	{
+		struct s_search_store_info_item* ssitem = &sd->searchstore.items[i];
+		struct item it;
+
+		WFIFOL(fd,i*blocksize+ 7) = ssitem->store_id;
+		WFIFOL(fd,i*blocksize+11) = ssitem->account_id;
+		memcpy(WFIFOP(fd,i*blocksize+15), ssitem->store_name, MESSAGE_SIZE);
+		WFIFOW(fd,i*blocksize+15+MESSAGE_SIZE) = ssitem->nameid;
+		WFIFOB(fd,i*blocksize+17+MESSAGE_SIZE) = itemtype(itemdb_type(ssitem->nameid));
+		WFIFOL(fd,i*blocksize+18+MESSAGE_SIZE) = ssitem->price;
+		WFIFOW(fd,i*blocksize+22+MESSAGE_SIZE) = ssitem->amount;
+		WFIFOB(fd,i*blocksize+24+MESSAGE_SIZE) = ssitem->refine;
+
+		// make-up an item for clif_addcards
+		memset(&it, 0, sizeof(it));
+		memcpy(&it.card, &ssitem->card, sizeof(it.card));
+		it.nameid = ssitem->nameid;
+		it.amount = ssitem->amount;
+
+		clif_addcards(WFIFOP(fd,i*blocksize+25+MESSAGE_SIZE), &it);
+	}
+
+	WFIFOSET(fd,WFIFOW(fd,2));
+}
+
+
+/// Notification of failure when searching for stores (ZC_SEARCH_STORE_INFO_FAILED)
+/// 0837 <reason>.B
+/// reason:
+///     0 = "No matching stores were found." (0x70b)
+///     1 = "There are too many results. Please enter more detailed search term." (0x6f8)
+///     2 = "You cannot search anymore." (0x706)
+///     3 = "You cannot search yet." (0x708)
+///     4 = "No sale (purchase) information available." (0x705)
+void clif_search_store_info_failed(struct map_session_data* sd, unsigned char reason)
+{
+	int fd = sd->fd;
+
+	WFIFOHEAD(fd,packet_len(0x837));
+	WFIFOW(fd,0) = 0x837;
+	WFIFOB(fd,2) = reason;
+	WFIFOSET(fd,packet_len(0x837));
+}
+
+
+/// Request to display next page of results (CZ_SEARCH_STORE_INFO_NEXT_PAGE)
+/// 0838
+static void clif_parse_SearchStoreInfoNextPage(int fd, struct map_session_data* sd)
+{
+	searchstore_next(sd);
+}
+
+
+/// Opens the search store window (ZC_OPEN_SEARCH_STORE_INFO)
+/// 083a <type>.W <remaining uses>.B
+/// type:
+///     0 = Search Stores
+///     1 = Search Stores (Cash), asks for confirmation, when clicking a store
+void clif_open_search_store_info(struct map_session_data* sd)
+{
+	int fd = sd->fd;
+
+	WFIFOHEAD(fd,packet_len(0x83a));
+	WFIFOW(fd,0) = 0x83a;
+	WFIFOW(fd,2) = sd->searchstore.effect;
+#if PACKETVER > 20100701
+	WFIFOB(fd,4) = (unsigned char)min(sd->searchstore.uses, UCHAR_MAX);
+#endif
+	WFIFOSET(fd,packet_len(0x83a));
+}
+
+
+/// Request to close the store search window (CZ_CLOSE_SEARCH_STORE_INFO)
+/// 083b
+static void clif_parse_CloseSearchStoreInfo(int fd, struct map_session_data* sd)
+{
+	searchstore_close(sd);
+}
+
+
+/// Request to invoke catalog effect on a store from search results (CZ_SSILIST_ITEM_CLICK)
+/// 083c <account id>.L <store id>.L <nameid>.W
+static void clif_parse_SearchStoreInfoListItemClick(int fd, struct map_session_data* sd)
+{
+	unsigned short nameid;
+	int account_id, store_id;
+	struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)];
+
+	account_id = RFIFOL(fd,info->pos[0]);
+	store_id   = RFIFOL(fd,info->pos[1]);
+	nameid     = RFIFOW(fd,info->pos[2]);
+
+	searchstore_click(sd, account_id, store_id, nameid);
+}
+
+
+/// Notification of the store position on current map (ZC_SSILIST_ITEM_CLICK_ACK)
+/// 083d <xPos>.W <yPos>.W
+void clif_search_store_info_click_ack(struct map_session_data* sd, short x, short y)
+{
+	int fd = sd->fd;
+
+	WFIFOHEAD(fd,packet_len(0x83d));
+	WFIFOW(fd,0) = 0x83d;
+	WFIFOW(fd,2) = x;
+	WFIFOW(fd,4) = y;
+	WFIFOSET(fd,packet_len(0x83d));
+}
+
+
 /*==========================================
  * ƒpƒPƒbƒgƒfƒoƒbƒO
  *------------------------------------------*/
@@ -14727,7 +14919,7 @@ static int packetdb_readdb(void)
 #endif
 	    3, -1,  8, -1,  86, 2,  6,  6, -1, -1,  4, 10, 10,  0,  0,  0,
 	    0,  0,  0,  0,  6,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
-	    0,  0,  0,  0,  0,  0,  0,  0,  0, 66,  0,  0,  0,  0,  0,  0,
+	    0,  0,  0,  0,  0, -1, -1,  3,  2, 66,  5,  2, 12,  6,  0,  0,
 	};
 	struct {
 		void (*func)(int, struct map_session_data *);
@@ -14923,6 +15115,11 @@ static int packetdb_readdb(void)
 		{clif_parse_ReqCloseBuyingStore,"reqclosebuyingstore"},
 		{clif_parse_ReqClickBuyingStore,"reqclickbuyingstore"},
 		{clif_parse_ReqTradeBuyingStore,"reqtradebuyingstore"},
+		// Store Search
+		{clif_parse_SearchStoreInfo,"searchstoreinfo"},
+		{clif_parse_SearchStoreInfoNextPage,"searchstoreinfonextpage"},
+		{clif_parse_CloseSearchStoreInfo,"closesearchstoreinfo"},
+		{clif_parse_SearchStoreInfoListItemClick,"searchstoreinfolistitemclick"},
 		{NULL,NULL}
 	};
 

+ 6 - 0
src/map/clif.h

@@ -619,4 +619,10 @@ void clif_buyingstore_update_item(struct map_session_data* sd, unsigned short na
 void clif_buyingstore_delete_item(struct map_session_data* sd, short index, unsigned short amount, int price);
 void clif_buyingstore_trade_failed_seller(struct map_session_data* sd, short result, unsigned short nameid);
 
+/// Search Store System
+void clif_search_store_info_ack(struct map_session_data* sd);
+void clif_search_store_info_failed(struct map_session_data* sd, unsigned char reason);
+void clif_open_search_store_info(struct map_session_data* sd);
+void clif_search_store_info_click_ack(struct map_session_data* sd, short x, short y);
+
 #endif /* _CLIF_H_ */

+ 3 - 0
src/map/pc.h

@@ -12,6 +12,7 @@
 #include "map.h" // RC_MAX
 #include "pc.h" // struct map_session_data
 #include "script.h" // struct script_reg, struct script_regstr
+#include "searchstore.h"  // struct s_search_store_info
 #include "status.h" // OPTION_*, struct weapon_atk
 #include "unit.h" // unit_stop_attack(), unit_stop_walking()
 #include "vending.h" // struct s_vending
@@ -360,6 +361,8 @@ struct map_session_data {
 	unsigned int buyer_id;  // uid of open buying store
 	struct s_buyingstore buyingstore;
 
+	struct s_search_store_info searchstore;
+
 	struct pet_data *pd;
 	struct homun_data *hd;	// [blackhole89]
 	struct mercenary_data *md;

+ 34 - 0
src/map/script.c

@@ -14831,6 +14831,39 @@ BUILDIN_FUNC(buyingstore)
 }
 
 
+/// Invokes search store info window
+/// searchstores <uses>,<effect>;
+BUILDIN_FUNC(searchstores)
+{
+	unsigned short effect;
+	unsigned int uses;
+	struct map_session_data* sd;
+
+	if( ( sd = script_rid2sd(st) ) == NULL )
+	{
+		return 0;
+	}
+
+	uses   = script_getnum(st,2);
+	effect = script_getnum(st,3);
+
+	if( !uses )
+	{
+		ShowError("buildin_searchstores: Amount of uses cannot be zero.\n");
+		return 1;
+	}
+
+	if( effect > 1 )
+	{
+		ShowError("buildin_searchstores: Invalid effect id %hu, specified.\n", effect);
+		return 1;
+	}
+
+	searchstore_open(sd, uses, effect);
+	return 0;
+}
+
+
 // declarations that were supposed to be exported from npc_chat.c
 #ifdef PCRE_SUPPORT
 BUILDIN_FUNC(defpattern);
@@ -15193,6 +15226,7 @@ struct script_function buildin_func[] = {
 	BUILDIN_DEF(progressbar,"si"),
 	BUILDIN_DEF(pushpc,"ii"),
 	BUILDIN_DEF(buyingstore,"i"),
+	BUILDIN_DEF(searchstores,"ii"),
 	// WoE SE
 	BUILDIN_DEF(agitstart2,""),
 	BUILDIN_DEF(agitend2,""),

+ 406 - 0
src/map/searchstore.c

@@ -0,0 +1,406 @@
+// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#include "../common/cbasetypes.h"
+#include "../common/malloc.h"  // aMalloc, aRealloc, aFree
+#include "../common/showmsg.h"  // ShowError, ShowWarning
+#include "../common/strlib.h"  // safestrncpy
+#include "atcommand.h"  // msg_txt
+#include "battle.h"  // battle_config.*
+#include "clif.h"  // clif_open_search_store_info, clif_search_store_info_*
+#include "pc.h"  // struct map_session_data, pc_setpos, pc_isGM
+#include "searchstore.h"  // struct s_search_store_info
+
+
+/// failure constants for clif functions
+enum e_searchstore_failure
+{
+	SSI_FAILED_NOTHING_SEARCH_ITEM         = 0,  // "No matching stores were found."
+	SSI_FAILED_OVER_MAXCOUNT               = 1,  // "There are too many results. Please enter more detailed search term."
+	SSI_FAILED_SEARCH_CNT                  = 2,  // "You cannot search anymore."
+	SSI_FAILED_LIMIT_SEARCH_TIME           = 3,  // "You cannot search yet."
+	SSI_FAILED_SSILIST_CLICK_TO_OPEN_STORE = 4,  // "No sale (purchase) information available."
+};
+
+
+enum e_searchstore_searchtype
+{
+	SEARCHTYPE_VENDING      = 0,
+	SEARCHTYPE_BUYING_STORE = 1,
+};
+
+
+enum e_searchstore_effecttype
+{
+	EFFECTTYPE_NORMAL = 0,
+	EFFECTTYPE_CASH   = 1,
+	EFFECTTYPE_MAX
+};
+
+
+/// type for shop search function
+typedef bool (*searchstore_search_t)(struct map_session_data* sd, unsigned short nameid);
+typedef bool (*searchstore_searchall_t)(struct map_session_data* sd, const struct s_search_store_search* s);
+
+
+/// retrieves search function by type
+static searchstore_search_t searchstore_getsearchfunc(unsigned char type)
+{
+	switch( type )
+	{
+		case SEARCHTYPE_VENDING:      return &vending_search;
+		case SEARCHTYPE_BUYING_STORE: return &buyingstore_search;
+	}
+	return NULL;
+}
+
+
+/// retrieves search-all function by type
+static searchstore_searchall_t searchstore_getsearchallfunc(unsigned char type)
+{
+	switch( type )
+	{
+		case SEARCHTYPE_VENDING:      return &vending_searchall;
+		case SEARCHTYPE_BUYING_STORE: return &buyingstore_searchall;
+	}
+	return NULL;
+}
+
+
+/// checks if the player has a store by type
+static bool searchstore_hasstore(struct map_session_data* sd, unsigned char type)
+{
+	switch( type )
+	{
+		case SEARCHTYPE_VENDING:      return (bool)( sd->vender_id != 0 );
+		case SEARCHTYPE_BUYING_STORE: return sd->state.buyingstore;
+	}
+	return false;
+}
+
+
+/// returns player's store id by type
+static int searchstore_getstoreid(struct map_session_data* sd, unsigned char type)
+{
+	switch( type )
+	{
+		case SEARCHTYPE_VENDING:      return sd->vender_id;
+		case SEARCHTYPE_BUYING_STORE: return sd->buyer_id;
+	}
+	return 0;
+}
+
+
+bool searchstore_open(struct map_session_data* sd, unsigned int uses, unsigned short effect)
+{
+	if( !battle_config.feature_search_stores || sd->searchstore.open )
+	{
+		return false;
+	}
+
+	if( !uses || effect >= EFFECTTYPE_MAX )
+	{// invalid input
+		return false;
+	}
+
+	sd->searchstore.open   = true;
+	sd->searchstore.uses   = uses;
+	sd->searchstore.effect = effect;
+
+	clif_open_search_store_info(sd);
+
+	return true;
+}
+
+
+void searchstore_query(struct map_session_data* sd, unsigned char type, unsigned int min_price, unsigned int max_price, const unsigned short* itemlist, unsigned int item_count, const unsigned short* cardlist, unsigned int card_count)
+{
+	unsigned int i;
+	struct map_session_data* pl_sd;
+	struct s_mapiterator* iter;
+	struct s_search_store_search s;
+	searchstore_searchall_t store_searchall;
+	time_t querytime;
+
+	if( !battle_config.feature_search_stores )
+	{
+		return;
+	}
+
+	if( !sd->searchstore.open )
+	{
+		return;
+	}
+
+	if( ( store_searchall = searchstore_getsearchallfunc(type) ) == NULL )
+	{
+		ShowError("searchstore_query: Unknown search type %u (account_id=%d).\n", (unsigned int)type, sd->bl.id);
+		return;
+	}
+
+	time(&querytime);
+
+	if( sd->searchstore.nextquerytime > querytime )
+	{
+		clif_search_store_info_failed(sd, SSI_FAILED_LIMIT_SEARCH_TIME);
+		return;
+	}
+
+	if( !sd->searchstore.uses )
+	{
+		clif_search_store_info_failed(sd, SSI_FAILED_SEARCH_CNT);
+		return;
+	}
+
+	// validate lists
+	for( i = 0; i < item_count; i++ )
+	{
+		if( !itemdb_exists(itemlist[i]) )
+		{
+			ShowWarning("searchstore_query: Client resolved item %hu is not known.\n", itemlist[i]);
+			clif_search_store_info_failed(sd, SSI_FAILED_NOTHING_SEARCH_ITEM);
+			return;
+		}
+	}
+	for( i = 0; i < card_count; i++ )
+	{
+		if( !itemdb_exists(cardlist[i]) )
+		{
+			ShowWarning("searchstore_query: Client resolved card %hu is not known.\n", cardlist[i]);
+			clif_search_store_info_failed(sd, SSI_FAILED_NOTHING_SEARCH_ITEM);
+			return;
+		}
+	}
+
+	if( max_price < min_price )
+	{
+		swap(min_price, max_price);
+	}
+
+	sd->searchstore.uses--;
+	sd->searchstore.type = type;
+	sd->searchstore.nextquerytime = querytime+battle_config.searchstore_querydelay;
+
+	// drop previous results
+	searchstore_clear(sd);
+
+	// allocate max. amount of results
+	sd->searchstore.items = aMalloc(sizeof(struct s_search_store_info_item)*battle_config.searchstore_maxresults);
+
+	// search
+	s.search_sd  = sd;
+	s.itemlist   = itemlist;
+	s.cardlist   = cardlist;
+	s.item_count = item_count;
+	s.card_count = card_count;
+	s.min_price  = min_price;
+	s.max_price  = max_price;
+	iter         = mapit_geteachpc();
+
+	for( pl_sd = (struct map_session_data*)mapit_first(iter); mapit_exists(iter);  pl_sd = (struct map_session_data*)mapit_next(iter) )
+	{
+		if( sd == pl_sd )
+		{// skip own shop, if any
+			continue;
+		}
+
+		if( !store_searchall(pl_sd, &s) )
+		{// exceeded result size
+			clif_search_store_info_failed(sd, SSI_FAILED_OVER_MAXCOUNT);
+			break;
+		}
+	}
+
+	mapit_free(iter);
+
+	if( sd->searchstore.count )
+	{
+		// reclaim unused memory
+		sd->searchstore.items = aRealloc(sd->searchstore.items, sizeof(struct s_search_store_info_item)*sd->searchstore.count);
+
+		// present results
+		clif_search_store_info_ack(sd);
+
+		// one page displayed
+		sd->searchstore.pages++;
+	}
+	else
+	{
+		// cleanup
+		searchstore_clear(sd);
+
+		// update uses
+		clif_search_store_info_ack(sd);
+
+		// notify of failure
+		clif_search_store_info_failed(sd, SSI_FAILED_NOTHING_SEARCH_ITEM);
+	}
+}
+
+
+/// checks whether or not more results are available for the client
+bool searchstore_querynext(struct map_session_data* sd)
+{
+	if( sd->searchstore.count && ( sd->searchstore.count-1 )/SEARCHSTORE_RESULTS_PER_PAGE < sd->searchstore.pages )
+	{
+		return true;
+	}
+
+	return false;
+}
+
+
+void searchstore_next(struct map_session_data* sd)
+{
+	if( !battle_config.feature_search_stores || !sd->searchstore.open || sd->searchstore.count <= sd->searchstore.pages*SEARCHSTORE_RESULTS_PER_PAGE )
+	{// nothing (more) to display
+		return;
+	}
+
+	// present results
+	clif_search_store_info_ack(sd);
+
+	// one more page displayed
+	sd->searchstore.pages++;
+}
+
+
+void searchstore_clear(struct map_session_data* sd)
+{
+	searchstore_clearremote(sd);
+
+	if( sd->searchstore.items )
+	{// release results
+		aFree(sd->searchstore.items);
+		sd->searchstore.items = NULL;
+	}
+
+	sd->searchstore.count = 0;
+	sd->searchstore.pages = 0;
+}
+
+
+void searchstore_close(struct map_session_data* sd)
+{
+	if( sd->searchstore.open )
+	{
+		searchstore_clear(sd);
+
+		sd->searchstore.uses = 0;
+		sd->searchstore.open = false;
+	}
+}
+
+
+void searchstore_click(struct map_session_data* sd, int account_id, int store_id, unsigned short nameid)
+{
+	unsigned int i;
+	struct map_session_data* pl_sd;
+	searchstore_search_t store_search;
+
+	if( !battle_config.feature_search_stores || !sd->searchstore.open || !sd->searchstore.count )
+	{
+		return;
+	}
+
+	searchstore_clearremote(sd);
+
+	ARR_FIND( 0, sd->searchstore.count, i,  sd->searchstore.items[i].store_id == store_id && sd->searchstore.items[i].account_id == account_id && sd->searchstore.items[i].nameid == nameid );
+	if( i == sd->searchstore.count )
+	{// no such result, crafted
+		ShowWarning("searchstore_click: Received request with item %hu of account %d, which is not part of current result set (account_id=%d, char_id=%d).\n", nameid, account_id, sd->bl.id, sd->status.char_id);
+		clif_search_store_info_failed(sd, SSI_FAILED_SSILIST_CLICK_TO_OPEN_STORE);
+		return;
+	}
+
+	if( ( pl_sd = map_id2sd(account_id) ) == NULL )
+	{// no longer online
+		clif_search_store_info_failed(sd, SSI_FAILED_SSILIST_CLICK_TO_OPEN_STORE);
+		return;
+	}
+
+	if( !searchstore_hasstore(pl_sd, sd->searchstore.type) || searchstore_getstoreid(pl_sd, sd->searchstore.type) != store_id )
+	{// no longer vending/buying or not same shop
+		clif_search_store_info_failed(sd, SSI_FAILED_SSILIST_CLICK_TO_OPEN_STORE);
+		return;
+	}
+
+	store_search = searchstore_getsearchfunc(sd->searchstore.type);
+
+	if( !store_search(pl_sd, nameid) )
+	{// item no longer being sold/bought
+		clif_search_store_info_failed(sd, SSI_FAILED_SSILIST_CLICK_TO_OPEN_STORE);
+		return;
+	}
+
+	switch( sd->searchstore.effect )
+	{
+		case EFFECTTYPE_NORMAL:
+			// display coords
+
+			if( sd->bl.m != pl_sd->bl.m )
+			{// not on same map, wipe previous marker
+				clif_search_store_info_click_ack(sd, -1, -1);
+			}
+			else
+			{
+				clif_search_store_info_click_ack(sd, pl_sd->bl.x, pl_sd->bl.y);
+			}
+
+			break;
+		case EFFECTTYPE_CASH:
+			// open remotely
+
+			// to bypass range checks
+			sd->searchstore.remote_id = account_id;
+
+			switch( sd->searchstore.type )
+			{
+				case SEARCHTYPE_VENDING:      vending_vendinglistreq(sd, account_id); break;
+				case SEARCHTYPE_BUYING_STORE: buyingstore_open(sd, account_id);       break;
+			}
+
+			break;
+		default:
+			// unknown
+			ShowError("searchstore_click: Unknown search store effect %u (account_id=%d).\n", (unsigned int)sd->searchstore.effect, sd->bl.id);
+	}
+}
+
+
+/// checks whether or not sd has opened account_id's shop remotely
+bool searchstore_queryremote(struct map_session_data* sd, int account_id)
+{
+	return (bool)( sd->searchstore.open && sd->searchstore.count && sd->searchstore.remote_id == account_id );
+}
+
+
+/// removes range-check bypassing for remotely opened stores
+void searchstore_clearremote(struct map_session_data* sd)
+{
+	sd->searchstore.remote_id = 0;
+}
+
+
+/// receives results from a store-specific callback
+bool searchstore_result(struct map_session_data* sd, int store_id, int account_id, const char* store_name, unsigned short nameid, unsigned short amount, unsigned int price, const short* card, unsigned char refine)
+{
+	struct s_search_store_info_item* ssitem;
+
+	if( sd->searchstore.count >= (unsigned int)battle_config.searchstore_maxresults )
+	{// no more
+		return false;
+	}
+
+	ssitem = &sd->searchstore.items[sd->searchstore.count++];
+	ssitem->store_id = store_id;
+	ssitem->account_id = account_id;
+	safestrncpy(ssitem->store_name, store_name, sizeof(ssitem->store_name));
+	ssitem->nameid = nameid;
+	ssitem->amount = amount;
+	ssitem->price = price;
+	memcpy(ssitem->card, card, sizeof(ssitem->card));
+	ssitem->refine = refine;
+
+	return true;
+}

+ 57 - 0
src/map/searchstore.h

@@ -0,0 +1,57 @@
+// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#ifndef _SEARCHSTORE_H_
+#define _SEARCHSTORE_H_
+
+#define SEARCHSTORE_RESULTS_PER_PAGE 10
+
+/// information about the search being performed
+struct s_search_store_search
+{
+	struct map_session_data* search_sd;  // sd of the searching player
+	const unsigned short* itemlist;
+	const unsigned short* cardlist;
+	unsigned int item_count;
+	unsigned int card_count;
+	unsigned int min_price;
+	unsigned int max_price;
+};
+
+struct s_search_store_info_item
+{
+	int store_id;
+	int account_id;
+	char store_name[MESSAGE_SIZE];
+	unsigned short nameid;
+	unsigned short amount;
+	unsigned int price;
+	short card[MAX_SLOTS];
+	unsigned char refine;
+};
+
+struct s_search_store_info
+{
+	unsigned int count;
+	struct s_search_store_info_item* items;
+	unsigned int pages;  // amount of pages already sent to client
+	unsigned int uses;
+	int remote_id;
+	time_t nextquerytime;
+	unsigned short effect;  // 0 = Normal (display coords), 1 = Cash (remote open store)
+	unsigned char type;  // 0 = Vending, 1 = Buying Store
+	bool open;
+};
+
+bool searchstore_open(struct map_session_data* sd, unsigned int uses, unsigned short effect);
+void searchstore_query(struct map_session_data* sd, unsigned char type, unsigned int min_price, unsigned int max_price, const unsigned short* itemlist, unsigned int item_count, const unsigned short* cardlist, unsigned int card_count);
+bool searchstore_querynext(struct map_session_data* sd);
+void searchstore_next(struct map_session_data* sd);
+void searchstore_clear(struct map_session_data* sd);
+void searchstore_close(struct map_session_data* sd);
+void searchstore_click(struct map_session_data* sd, int account_id, int store_id, unsigned short nameid);
+bool searchstore_queryremote(struct map_session_data* sd, int account_id);
+void searchstore_clearremote(struct map_session_data* sd);
+bool searchstore_result(struct map_session_data* sd, int store_id, int account_id, const char* store_name, unsigned short nameid, unsigned short amount, unsigned int price, const short* card, unsigned char refine);
+
+#endif  // _SEARCHSTORE_H_

+ 1 - 0
src/map/unit.c

@@ -1874,6 +1874,7 @@ int unit_remove_map_(struct block_list *bl, clr_type clrtype, const char* file,
 		if(sd->vender_id)
 			vending_closevending(sd);
 		buyingstore_close(sd);
+		searchstore_close(sd);
 		if(sd->state.storage_flag == 1)
 			storage_storage_quit(sd,0);
 		else if (sd->state.storage_flag == 2)

+ 89 - 1
src/map/vending.c

@@ -87,8 +87,11 @@ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const ui
 		return;
 	}
 
-	if( sd->bl.m != vsd->bl.m || !check_distance_bl(&sd->bl, &vsd->bl, AREA_SIZE) )
+	if( !searchstore_queryremote(sd, aid) && ( sd->bl.m != vsd->bl.m || !check_distance_bl(&sd->bl, &vsd->bl, AREA_SIZE) ) )
 		return; // shop too far away
+
+	searchstore_clearremote(sd);
+
 	if( count < 1 || count > MAX_VENDING || count > vsd->vend_num )
 		return; // invalid amount of purchased items
 
@@ -314,3 +317,88 @@ void vending_openvending(struct map_session_data* sd, const char* message, bool
 	clif_openvending(sd,sd->bl.id,sd->vending);
 	clif_showvendingboard(&sd->bl,message,0);
 }
+
+
+/// Checks if an item is being sold in given player's vending.
+bool vending_search(struct map_session_data* sd, unsigned short nameid)
+{
+	int i;
+
+	if( !sd->vender_id )
+	{// not vending
+		return false;
+	}
+
+	ARR_FIND( 0, sd->vend_num, i, sd->status.cart[sd->vending[i].index].nameid == (short)nameid );
+	if( i == sd->vend_num )
+	{// not found
+		return false;
+	}
+
+	return true;
+}
+
+
+/// Searches for all items in a vending, that match given ids, price and possible cards.
+/// @return Whether or not the search should be continued.
+bool vending_searchall(struct map_session_data* sd, const struct s_search_store_search* s)
+{
+	int i, c, slot;
+	unsigned int idx, cidx;
+	struct item* it;
+
+	if( !sd->vender_id )
+	{// not vending
+		return true;
+	}
+
+	for( idx = 0; idx < s->item_count; idx++ )
+	{
+		ARR_FIND( 0, sd->vend_num, i, sd->status.cart[sd->vending[i].index].nameid == (short)s->itemlist[idx] );
+		if( i == sd->vend_num )
+		{// not found
+			continue;
+		}
+		it = &sd->status.cart[sd->vending[i].index];
+
+		if( s->min_price && s->min_price > sd->vending[i].value )
+		{// too low price
+			continue;
+		}
+
+		if( s->max_price && s->max_price < sd->vending[i].value )
+		{// too high price
+			continue;
+		}
+
+		if( s->card_count )
+		{// check cards
+			if( itemdb_isspecial(it->card[0]) )
+			{// something, that is not a carded
+				continue;
+			}
+			slot = itemdb_slot(it->nameid);
+
+			for( c = 0; c < slot && it->card[c]; c ++ )
+			{
+				ARR_FIND( 0, s->card_count, cidx, s->cardlist[cidx] == it->card[c] );
+				if( cidx != s->card_count )
+				{// found
+					break;
+				}
+			}
+
+			if( c == slot || !it->card[c] )
+			{// no card match
+				continue;
+			}
+		}
+
+		if( !searchstore_result(s->search_sd, sd->vender_id, sd->status.account_id, sd->message, it->nameid, sd->vending[i].amount, sd->vending[i].value, it->card, it->refine) )
+		{// result set full
+			return false;
+		}
+	}
+
+	return true;
+}

+ 3 - 0
src/map/vending.h

@@ -7,6 +7,7 @@
 #include "../common/cbasetypes.h"
 //#include "map.h"
 struct map_session_data;
+struct s_search_store_search;
 
 struct s_vending {
 	short index;
@@ -18,5 +19,7 @@ void vending_closevending(struct map_session_data* sd);
 void vending_openvending(struct map_session_data* sd, const char* message, bool flag, const uint8* data, int count);
 void vending_vendinglistreq(struct map_session_data* sd, int id);
 void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const uint8* data, int count);
+bool vending_search(struct map_session_data* sd, unsigned short nameid);
+bool vending_searchall(struct map_session_data* sd, const struct s_search_store_search* s);
 
 #endif /* _VENDING_H_ */

+ 2 - 0
vcproj-10/map-server_sql.vcxproj

@@ -169,6 +169,7 @@
     <ClInclude Include="..\src\map\pet.h" />
     <ClInclude Include="..\src\map\quest.h" />
     <ClInclude Include="..\src\map\script.h" />
+    <ClInclude Include="..\src\map\searchstore.h" />
     <ClInclude Include="..\src\map\skill.h" />
     <ClInclude Include="..\src\map\status.h" />
     <ClInclude Include="..\src\map\storage.h" />
@@ -220,6 +221,7 @@
     <ClCompile Include="..\src\map\pet.c" />
     <ClCompile Include="..\src\map\quest.c" />
     <ClCompile Include="..\src\map\script.c" />
+    <ClCompile Include="..\src\map\searchstore.c" />
     <ClCompile Include="..\src\map\skill.c" />
     <ClCompile Include="..\src\map\status.c" />
     <ClCompile Include="..\src\map\storage.c" />

+ 2 - 0
vcproj-10/map-server_txt.vcxproj

@@ -149,6 +149,7 @@
     <ClCompile Include="..\src\map\pet.c" />
     <ClCompile Include="..\src\map\quest.c" />
     <ClCompile Include="..\src\map\script.c" />
+    <ClCompile Include="..\src\map\searchstore.c" />
     <ClCompile Include="..\src\map\skill.c" />
     <ClCompile Include="..\src\map\status.c" />
     <ClCompile Include="..\src\map\storage.c" />
@@ -198,6 +199,7 @@
     <ClInclude Include="..\src\map\pet.h" />
     <ClInclude Include="..\src\map\quest.h" />
     <ClInclude Include="..\src\map\script.h" />
+    <ClInclude Include="..\src\map\searchstore.h" />
     <ClInclude Include="..\src\map\skill.h" />
     <ClInclude Include="..\src\map\status.h" />
     <ClInclude Include="..\src\map\storage.h" />

+ 8 - 0
vcproj-6/map-server_sql.dsp

@@ -419,6 +419,14 @@ SOURCE=..\src\map\script.h
 # End Source File
 # Begin Source File
 
+SOURCE=..\src\map\searchstore.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\src\map\searchstore.h
+# End Source File
+# Begin Source File
+
 SOURCE=..\src\map\skill.c
 # End Source File
 # Begin Source File

+ 8 - 0
vcproj-6/map-server_txt.dsp

@@ -303,6 +303,10 @@ SOURCE=..\src\map\script.c
 # End Source File
 # Begin Source File
 
+SOURCE=..\src\map\searchstore.c
+# End Source File
+# Begin Source File
+
 SOURCE=..\src\map\skill.c
 # End Source File
 # Begin Source File
@@ -435,6 +439,10 @@ SOURCE=..\src\map\script.h
 # End Source File
 # Begin Source File
 
+SOURCE=..\src\map\searchstore.h
+# End Source File
+# Begin Source File
+
 SOURCE=..\src\map\skill.h
 # End Source File
 # Begin Source File

+ 6 - 0
vcproj-7.1/map-server_sql.vcproj

@@ -307,6 +307,12 @@
 			<File
 				RelativePath="..\src\map\script.h">
 			</File>
+			<File
+				RelativePath="..\src\map\searchstore.c">
+			</File>
+			<File
+				RelativePath="..\src\map\searchstore.h">
+			</File>
 			<File
 				RelativePath="..\src\map\skill.c">
 			</File>

+ 6 - 0
vcproj-7.1/map-server_txt.vcproj

@@ -307,6 +307,12 @@
 			<File
 				RelativePath="..\src\map\script.h">
 			</File>
+			<File
+				RelativePath="..\src\map\searchstore.c">
+			</File>
+			<File
+				RelativePath="..\src\map\searchstore.h">
+			</File>
 			<File
 				RelativePath="..\src\map\skill.c">
 			</File>

+ 8 - 0
vcproj-8/map-server_sql.vcproj

@@ -563,6 +563,14 @@
 				RelativePath="..\src\map\script.h"
 				>
 			</File>
+			<File
+				RelativePath="..\src\map\searchstore.c"
+				>
+			</File>
+			<File
+				RelativePath="..\src\map\searchstore.h"
+				>
+			</File>
 			<File
 				RelativePath="..\src\map\skill.c"
 				>

+ 8 - 0
vcproj-8/map-server_txt.vcproj

@@ -414,6 +414,14 @@
 				RelativePath="..\src\map\script.h"
 				>
 			</File>
+			<File
+				RelativePath="..\src\map\searchstore.c"
+				>
+			</File>
+			<File
+				RelativePath="..\src\map\searchstore.h"
+				>
+			</File>
 			<File
 				RelativePath="..\src\map\skill.c"
 				>

+ 8 - 0
vcproj-9/map-server_sql.vcproj

@@ -562,6 +562,14 @@
 				RelativePath="..\src\map\script.h"
 				>
 			</File>
+			<File
+				RelativePath="..\src\map\searchstore.c"
+				>
+			</File>
+			<File
+				RelativePath="..\src\map\searchstore.h"
+				>
+			</File>
 			<File
 				RelativePath="..\src\map\skill.c"
 				>

+ 8 - 0
vcproj-9/map-server_txt.vcproj

@@ -413,6 +413,14 @@
 				RelativePath="..\src\map\script.h"
 				>
 			</File>
+			<File
+				RelativePath="..\src\map\searchstore.c"
+				>
+			</File>
+			<File
+				RelativePath="..\src\map\searchstore.h"
+				>
+			</File>
 			<File
 				RelativePath="..\src\map\skill.c"
 				>