* 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
This commit is contained in:
ai4rei 2011-03-06 19:16:09 +00:00
parent ffe2f08b20
commit 6b74ef9b50
32 changed files with 1006 additions and 20 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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, },

View File

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

View File

@ -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;
}

View File

@ -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_

View File

@ -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}
};

View File

@ -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_ */

View File

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

View File

@ -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
src/map/searchstore.c Normal file
View File

@ -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
src/map/searchstore.h Normal file
View File

@ -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_

View File

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

View File

@ -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;
}

View File

@ -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_ */

View File

@ -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" />

View File

@ -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" />

View File

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

View File

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

View File

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

View File

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

View File

@ -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"
>

View File

@ -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"
>

View File

@ -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"
>

View File

@ -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"
>