diff options
Diffstat (limited to 'src/map/searchstore.c')
-rw-r--r-- | src/map/searchstore.c | 406 |
1 files changed, 406 insertions, 0 deletions
diff --git a/src/map/searchstore.c b/src/map/searchstore.c new file mode 100644 index 000000000..9c9de1f91 --- /dev/null +++ b/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; +} |