searchstore.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. // Copyright (c) rAthena Dev Teams - Licensed under GNU GPL
  2. // For more information, see LICENCE in the main folder
  3. #include "searchstore.hpp" // struct s_search_store_info
  4. #include "../common/cbasetypes.hpp"
  5. #include "../common/malloc.hpp" // aMalloc, aRealloc, aFree
  6. #include "../common/showmsg.hpp" // ShowError, ShowWarning
  7. #include "../common/strlib.hpp" // safestrncpy
  8. #include "battle.hpp" // battle_config.*
  9. #include "clif.hpp" // clif_open_search_store_info, clif_search_store_info_*
  10. #include "pc.hpp" // struct map_session_data
  11. /// Failure constants for clif functions
  12. enum e_searchstore_failure
  13. {
  14. SSI_FAILED_NOTHING_SEARCH_ITEM = 0, // "No matching stores were found."
  15. SSI_FAILED_OVER_MAXCOUNT = 1, // "There are too many results. Please enter more detailed search term."
  16. SSI_FAILED_SEARCH_CNT = 2, // "You cannot search anymore."
  17. SSI_FAILED_LIMIT_SEARCH_TIME = 3, // "You cannot search yet."
  18. SSI_FAILED_SSILIST_CLICK_TO_OPEN_STORE = 4, // "No sale (purchase) information available."
  19. };
  20. /// Search type constants
  21. enum e_searchstore_searchtype
  22. {
  23. SEARCHTYPE_VENDING = 0,
  24. SEARCHTYPE_BUYING_STORE = 1,
  25. };
  26. /// Search effect constants
  27. enum e_searchstore_effecttype
  28. {
  29. EFFECTTYPE_NORMAL = 0,
  30. EFFECTTYPE_CASH = 1,
  31. EFFECTTYPE_MAX
  32. };
  33. /// Type for shop search function
  34. typedef bool (*searchstore_search_t)(struct map_session_data* sd, unsigned short nameid);
  35. typedef bool (*searchstore_searchall_t)(struct map_session_data* sd, const struct s_search_store_search* s);
  36. /**
  37. * Retrieves search function by type.
  38. * @param type : type of search to conduct
  39. * @return : search type
  40. */
  41. static searchstore_search_t searchstore_getsearchfunc(unsigned char type)
  42. {
  43. switch( type ) {
  44. case SEARCHTYPE_VENDING: return &vending_search;
  45. case SEARCHTYPE_BUYING_STORE: return &buyingstore_search;
  46. }
  47. return NULL;
  48. }
  49. /**
  50. * Retrieves search-all function by type.
  51. * @param type : type of search to conduct
  52. * @return : search type
  53. */
  54. static searchstore_searchall_t searchstore_getsearchallfunc(unsigned char type)
  55. {
  56. switch( type ) {
  57. case SEARCHTYPE_VENDING: return &vending_searchall;
  58. case SEARCHTYPE_BUYING_STORE: return &buyingstore_searchall;
  59. }
  60. return NULL;
  61. }
  62. /**
  63. * Checks if the player has a store by type.
  64. * @param sd : player requesting
  65. * @param type : type of search to conduct
  66. * @return : store type
  67. */
  68. static bool searchstore_hasstore(struct map_session_data* sd, unsigned char type)
  69. {
  70. switch( type ) {
  71. case SEARCHTYPE_VENDING: return sd->state.vending;
  72. case SEARCHTYPE_BUYING_STORE: return sd->state.buyingstore;
  73. }
  74. return false;
  75. }
  76. /**
  77. * Returns player's store ID by type.
  78. * @param sd : player requesting
  79. * @param type : type of search to conduct
  80. * @return : store ID
  81. */
  82. static int searchstore_getstoreid(struct map_session_data* sd, unsigned char type)
  83. {
  84. switch( type ) {
  85. case SEARCHTYPE_VENDING: return sd->vender_id;
  86. case SEARCHTYPE_BUYING_STORE: return sd->buyer_id;
  87. }
  88. return 0;
  89. }
  90. /**
  91. * Send request to open Search Store.
  92. * @param sd : player requesting
  93. * @param uses : uses left to open
  94. * @param effect : shop type
  95. * @return : true : opened, false : failed to open
  96. */
  97. bool searchstore_open(struct map_session_data* sd, unsigned int uses, unsigned short effect)
  98. {
  99. if( !battle_config.feature_search_stores || sd->searchstore.open )
  100. return false;
  101. if( !uses || effect >= EFFECTTYPE_MAX ) // invalid input
  102. return false;
  103. sd->searchstore.open = true;
  104. sd->searchstore.uses = uses;
  105. sd->searchstore.effect = effect;
  106. clif_open_search_store_info(sd);
  107. return true;
  108. }
  109. /**
  110. * Query and present the results for the item.
  111. * @param sd : player requesting
  112. * @param type : shop type
  113. * @param min_price : minimum zeny price
  114. * @param max_price : maximum zeny price
  115. * @param itemlist : list with stored item results
  116. * @param item_count : amount of items in itemlist
  117. * @param cardlist : list with stored cards (cards attached to items)
  118. * @param card_count : amount of items in cardlist
  119. */
  120. void searchstore_query(struct map_session_data* sd, unsigned char type, unsigned int min_price, unsigned int max_price, const struct PACKET_CZ_SEARCH_STORE_INFO_item* itemlist, unsigned int item_count, const struct PACKET_CZ_SEARCH_STORE_INFO_item* cardlist, unsigned int card_count)
  121. {
  122. unsigned int i;
  123. struct map_session_data* pl_sd;
  124. struct DBIterator *iter;
  125. struct s_search_store_search s;
  126. searchstore_searchall_t store_searchall;
  127. time_t querytime;
  128. if( !battle_config.feature_search_stores )
  129. return;
  130. if( !sd->searchstore.open )
  131. return;
  132. if( ( store_searchall = searchstore_getsearchallfunc(type) ) == NULL ) {
  133. ShowError("searchstore_query: Unknown search type %u (account_id=%d).\n", (unsigned int)type, sd->bl.id);
  134. return;
  135. }
  136. time(&querytime);
  137. if( sd->searchstore.nextquerytime > querytime ) {
  138. clif_search_store_info_failed(sd, SSI_FAILED_LIMIT_SEARCH_TIME);
  139. return;
  140. }
  141. if( !sd->searchstore.uses ) {
  142. clif_search_store_info_failed(sd, SSI_FAILED_SEARCH_CNT);
  143. return;
  144. }
  145. // validate lists
  146. for( i = 0; i < item_count; i++ ) {
  147. if( !itemdb_exists(itemlist[i].itemId) ) {
  148. ShowWarning("searchstore_query: Client resolved item %hu is not known.\n", itemlist[i].itemId);
  149. clif_search_store_info_failed(sd, SSI_FAILED_NOTHING_SEARCH_ITEM);
  150. return;
  151. }
  152. }
  153. for( i = 0; i < card_count; i++ ) {
  154. if( !itemdb_exists(cardlist[i].itemId) ) {
  155. ShowWarning("searchstore_query: Client resolved card %hu is not known.\n", cardlist[i].itemId);
  156. clif_search_store_info_failed(sd, SSI_FAILED_NOTHING_SEARCH_ITEM);
  157. return;
  158. }
  159. }
  160. if( max_price < min_price )
  161. SWAP(min_price, max_price);
  162. sd->searchstore.uses--;
  163. sd->searchstore.type = type;
  164. sd->searchstore.nextquerytime = querytime+battle_config.searchstore_querydelay;
  165. // drop previous results
  166. searchstore_clear(sd);
  167. // allocate max. amount of results
  168. sd->searchstore.items = (struct s_search_store_info_item*)aMalloc(sizeof(struct s_search_store_info_item)*battle_config.searchstore_maxresults);
  169. // search
  170. s.search_sd = sd;
  171. s.itemlist = itemlist;
  172. s.cardlist = cardlist;
  173. s.item_count = item_count;
  174. s.card_count = card_count;
  175. s.min_price = min_price;
  176. s.max_price = max_price;
  177. iter = db_iterator((type == SEARCHTYPE_VENDING) ? vending_getdb() : buyingstore_getdb());
  178. for( pl_sd = (struct map_session_data*)dbi_first(iter); dbi_exists(iter); pl_sd = (struct map_session_data*)dbi_next(iter) ) {
  179. if( sd == pl_sd ) // skip own shop, if any
  180. continue;
  181. if( !store_searchall(pl_sd, &s) ) { // exceeded result size
  182. clif_search_store_info_failed(sd, SSI_FAILED_OVER_MAXCOUNT);
  183. break;
  184. }
  185. }
  186. dbi_destroy(iter);
  187. if( sd->searchstore.count ) {
  188. // reclaim unused memory
  189. sd->searchstore.items = (struct s_search_store_info_item*)aRealloc(sd->searchstore.items, sizeof(struct s_search_store_info_item)*sd->searchstore.count);
  190. // present results
  191. clif_search_store_info_ack(sd);
  192. // one page displayed
  193. sd->searchstore.pages++;
  194. } else {
  195. // cleanup
  196. searchstore_clear(sd);
  197. // update uses
  198. clif_search_store_info_ack(sd);
  199. // notify of failure
  200. clif_search_store_info_failed(sd, SSI_FAILED_NOTHING_SEARCH_ITEM);
  201. }
  202. }
  203. /**
  204. * Checks whether or not more results are available for the client.
  205. * @param sd : player requesting
  206. * @return : true : more items to search, false : no more items
  207. */
  208. bool searchstore_querynext(struct map_session_data* sd)
  209. {
  210. if( sd->searchstore.count && ( sd->searchstore.count-1 )/SEARCHSTORE_RESULTS_PER_PAGE < sd->searchstore.pages )
  211. return true;
  212. return false;
  213. }
  214. /**
  215. * Get and display the results for the next page.
  216. * @param sd : player requesting
  217. */
  218. void searchstore_next(struct map_session_data* sd)
  219. {
  220. if( !battle_config.feature_search_stores || !sd->searchstore.open || sd->searchstore.count <= sd->searchstore.pages*SEARCHSTORE_RESULTS_PER_PAGE ) // nothing (more) to display
  221. return;
  222. // present results
  223. clif_search_store_info_ack(sd);
  224. // one more page displayed
  225. sd->searchstore.pages++;
  226. }
  227. /**
  228. * Prepare to clear information for closing of window.
  229. * @param sd : player requesting
  230. */
  231. void searchstore_clear(struct map_session_data* sd)
  232. {
  233. searchstore_clearremote(sd);
  234. if( sd->searchstore.items ) { // release results
  235. aFree(sd->searchstore.items);
  236. sd->searchstore.items = NULL;
  237. }
  238. sd->searchstore.count = 0;
  239. sd->searchstore.pages = 0;
  240. }
  241. /**
  242. * Close the Search Store window.
  243. * @param sd : player requesting
  244. */
  245. void searchstore_close(struct map_session_data* sd)
  246. {
  247. if( sd->searchstore.open ) {
  248. searchstore_clear(sd);
  249. sd->searchstore.uses = 0;
  250. sd->searchstore.open = false;
  251. }
  252. }
  253. /**
  254. * Process the actions (click) for the Search Store window.
  255. * @param sd : player requesting
  256. * @param account_id : account ID of owner's shop
  257. * @param store_id : store ID created by client
  258. * @param nameid : item being searched
  259. */
  260. void searchstore_click(struct map_session_data* sd, uint32 account_id, int store_id, unsigned short nameid)
  261. {
  262. unsigned int i;
  263. struct map_session_data* pl_sd;
  264. searchstore_search_t store_search;
  265. if( !battle_config.feature_search_stores || !sd->searchstore.open || !sd->searchstore.count )
  266. return;
  267. searchstore_clearremote(sd);
  268. 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 );
  269. if( i == sd->searchstore.count ) { // no such result, crafted
  270. 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);
  271. clif_search_store_info_failed(sd, SSI_FAILED_SSILIST_CLICK_TO_OPEN_STORE);
  272. return;
  273. }
  274. if( ( pl_sd = map_id2sd(account_id) ) == NULL ) { // no longer online
  275. clif_search_store_info_failed(sd, SSI_FAILED_SSILIST_CLICK_TO_OPEN_STORE);
  276. return;
  277. }
  278. 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
  279. clif_search_store_info_failed(sd, SSI_FAILED_SSILIST_CLICK_TO_OPEN_STORE);
  280. return;
  281. }
  282. store_search = searchstore_getsearchfunc(sd->searchstore.type);
  283. if( !store_search(pl_sd, nameid) ) {// item no longer being sold/bought
  284. clif_search_store_info_failed(sd, SSI_FAILED_SSILIST_CLICK_TO_OPEN_STORE);
  285. return;
  286. }
  287. switch( sd->searchstore.effect ) {
  288. case EFFECTTYPE_NORMAL:
  289. // display coords
  290. if( sd->bl.m != pl_sd->bl.m ) // not on same map, wipe previous marker
  291. clif_search_store_info_click_ack(sd, -1, -1);
  292. else
  293. clif_search_store_info_click_ack(sd, pl_sd->bl.x, pl_sd->bl.y);
  294. break;
  295. case EFFECTTYPE_CASH:
  296. // open remotely
  297. // to bypass range checks
  298. sd->searchstore.remote_id = account_id;
  299. switch( sd->searchstore.type ) {
  300. case SEARCHTYPE_VENDING: vending_vendinglistreq(sd, account_id); break;
  301. case SEARCHTYPE_BUYING_STORE: buyingstore_open(sd, account_id); break;
  302. }
  303. break;
  304. default:
  305. // unknown
  306. ShowError("searchstore_click: Unknown search store effect %u (account_id=%d).\n", (unsigned int)sd->searchstore.effect, sd->bl.id);
  307. }
  308. }
  309. /**
  310. * Checks whether or not sd has opened account_id's shop remotely.
  311. * @param sd : player requesting
  312. * @param account_id : account ID of owner's shop
  313. * @return : true : shop opened, false : shop not opened
  314. */
  315. bool searchstore_queryremote(struct map_session_data* sd, uint32 account_id)
  316. {
  317. return (bool)( sd->searchstore.open && sd->searchstore.count && sd->searchstore.remote_id == account_id );
  318. }
  319. /**
  320. * Removes range-check bypassing for remotely opened stores.
  321. * @param sd : player requesting
  322. */
  323. void searchstore_clearremote(struct map_session_data* sd)
  324. {
  325. sd->searchstore.remote_id = 0;
  326. }
  327. /**
  328. * Receives results from a store-specific callback.
  329. * @param sd : player requesting
  330. * @param store_id : store ID generated by the client
  331. * @param account_id : account ID of owner's shop
  332. * @param store_name : name of store
  333. * @param nameid : item being searched
  334. * @param amount : count of item
  335. * @param price : zeny price of item
  336. * @param card : card in the item
  337. * @param refine : refine of the item
  338. */
  339. bool searchstore_result(struct map_session_data* sd, int store_id, uint32 account_id, const char* store_name, unsigned short nameid, unsigned short amount, unsigned int price, const unsigned short* card, unsigned char refine)
  340. {
  341. struct s_search_store_info_item* ssitem;
  342. if( sd->searchstore.count >= (unsigned int)battle_config.searchstore_maxresults ) // no more
  343. return false;
  344. ssitem = &sd->searchstore.items[sd->searchstore.count++];
  345. ssitem->store_id = store_id;
  346. ssitem->account_id = account_id;
  347. safestrncpy(ssitem->store_name, store_name, sizeof(ssitem->store_name));
  348. ssitem->nameid = nameid;
  349. ssitem->amount = amount;
  350. ssitem->price = price;
  351. memcpy(ssitem->card, card, sizeof(ssitem->card));
  352. ssitem->refine = refine;
  353. return true;
  354. }