1 // Copyright (c) Athena Dev Teams - Licensed under GNU GPL
2 // For more information, see LICENCE in the main folder
4 #include "../common/nullpo.h"
5 #include "../common/malloc.h" // aMalloc, aFree
6 #include "../common/showmsg.h" // ShowInfo
7 #include "../common/strlib.h"
10 #include "atcommand.h"
15 #include "buyingstore.h" // struct s_autotrade_entry, struct s_autotrader
16 #include "achievement.h"
18 #include <stdlib.h> // atoi
20 static uint32 vending_nextid = 0; ///Vending_id counter
21 static DBMap *vending_db; ///DB holder the vender : charid -> map_session_data
24 static DBMap *vending_autotrader_db; /// Holds autotrader info: char_id -> struct s_autotrader
25 static void vending_autotrader_remove(struct s_autotrader *at, bool remove);
26 static int vending_autotrader_free(DBKey key, DBData *data, va_list ap);
29 * Lookup to get the vending_db outside module
30 * @return the vending_db
32 DBMap * vending_getdb()
38 * Create an unique vending shop id.
39 * @return the next vending_id
41 static int vending_getuid(void)
43 return ++vending_nextid;
47 * Make a player close his shop
48 * @param sd : player session
50 void vending_closevending(struct map_session_data* sd)
54 if( sd->state.vending ) {
55 if( Sql_Query( mmysql_handle, "DELETE FROM `%s` WHERE vending_id = %d;", vending_items_table, sd->vender_id ) != SQL_SUCCESS ||
56 Sql_Query( mmysql_handle, "DELETE FROM `%s` WHERE `id` = %d;", vendings_table, sd->vender_id ) != SQL_SUCCESS ) {
57 Sql_ShowDebug(mmysql_handle);
60 sd->state.vending = false;
62 clif_closevendingboard(&sd->bl, 0);
63 idb_remove(vending_db, sd->status.char_id);
68 * Player request a shop's item list (a player shop)
69 * @param sd : player requestion the list
70 * @param id : vender account id (gid)
72 void vending_vendinglistreq(struct map_session_data* sd, int id)
74 struct map_session_data* vsd;
77 if( (vsd = map_id2sd(id)) == NULL )
79 if( !vsd->state.vending )
80 return; // not vending
82 if (!pc_can_give_items(sd) || !pc_can_give_items(vsd)) { //check if both GMs are allowed to trade
83 clif_displaymessage(sd->fd, msg_txt(sd,246));
87 sd->vended_id = vsd->vender_id; // register vending uid
89 clif_vendinglist(sd, id, vsd->vending);
93 * Purchase item(s) from a shop
94 * @param sd : buyer player session
95 * @param aid : account id of vender
96 * @param uid : shop unique id
97 * @param data : items data who would like to purchase \n
98 * data := {<index>.w <amount>.w }[count]
99 * @param count : number of different items he's trying to buy
101 void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const uint8* data, int count)
103 int i, j, cursor, w, new_ = 0, blank, vend_list[MAX_VENDING];
105 struct s_vending vending[MAX_VENDING]; // against duplicate packets
106 struct map_session_data* vsd = map_id2sd(aid);
109 if( vsd == NULL || !vsd->state.vending || vsd->bl.id == sd->bl.id )
110 return; // invalid shop
112 if( vsd->vender_id != uid ) { // shop has changed
113 clif_buyvending(sd, 0, 0, 6); // store information was incorrect
117 if( !searchstore_queryremote(sd, aid) && ( sd->bl.m != vsd->bl.m || !check_distance_bl(&sd->bl, &vsd->bl, AREA_SIZE) ) )
118 return; // shop too far away
120 searchstore_clearremote(sd);
122 if( count < 1 || count > MAX_VENDING || count > vsd->vend_num )
123 return; // invalid amount of purchased items
125 blank = pc_inventoryblank(sd); //number of free cells in the buyer's inventory
127 // duplicate item in vending to check hacker with multiple packets
128 memcpy(&vending, &vsd->vending, sizeof(vsd->vending)); // copy vending list
131 z = 0.; // zeny counter
132 w = 0; // weight counter
133 for( i = 0; i < count; i++ ) {
134 short amount = *(uint16*)(data + 4*i + 0);
135 short idx = *(uint16*)(data + 4*i + 2);
141 // check of item index in the cart
142 if( idx < 0 || idx >= MAX_CART )
145 ARR_FIND( 0, vsd->vend_num, j, vsd->vending[j].index == idx );
146 if( j == vsd->vend_num )
147 return; //picked non-existing item
151 z += ((double)vsd->vending[j].value * (double)amount);
152 if( z > (double)sd->status.zeny || z < 0. || z > (double)MAX_ZENY ) {
153 clif_buyvending(sd, idx, amount, 1); // you don't have enough zeny
156 if( z + (double)vsd->status.zeny > (double)MAX_ZENY && !battle_config.vending_over_max ) {
157 clif_buyvending(sd, idx, vsd->vending[j].amount, 4); // too much zeny = overflow
161 w += itemdb_weight(vsd->cart.u.items_cart[idx].nameid) * amount;
162 if( w + sd->weight > sd->max_weight ) {
163 clif_buyvending(sd, idx, amount, 2); // you can not buy, because overweight
167 //Check to see if cart/vend info is in sync.
168 if( vending[j].amount > vsd->cart.u.items_cart[idx].amount )
169 vending[j].amount = vsd->cart.u.items_cart[idx].amount;
171 // if they try to add packets (example: get twice or more 2 apples if marchand has only 3 apples).
172 // here, we check cumulative amounts
173 if( vending[j].amount < amount ) {
174 // send more quantity is not a hack (an other player can have buy items just before)
175 clif_buyvending(sd, idx, vsd->vending[j].amount, 4); // not enough quantity
179 vending[j].amount -= amount;
181 switch( pc_checkadditem(sd, vsd->cart.u.items_cart[idx].nameid, amount) ) {
182 case CHKADDITEM_EXIST:
183 break; //We'd add this item to the existing one (in buyers inventory)
187 return; //Buyer has no space in his inventory
189 case CHKADDITEM_OVERAMOUNT:
190 return; //too many items
194 pc_payzeny(sd, (int)z, LOG_TYPE_VENDING, vsd);
195 achievement_update_objective(sd, AG_SPEND_ZENY, 1, (int)z);
196 if( battle_config.vending_tax )
197 z -= z * (battle_config.vending_tax/10000.);
198 pc_getzeny(vsd, (int)z, LOG_TYPE_VENDING, sd);
200 for( i = 0; i < count; i++ ) {
201 short amount = *(uint16*)(data + 4*i + 0);
202 short idx = *(uint16*)(data + 4*i + 2);
204 z = 0.; // zeny counter
207 pc_additem(sd, &vsd->cart.u.items_cart[idx], amount, LOG_TYPE_VENDING);
208 vsd->vending[vend_list[i]].amount -= amount;
209 z += ((double)vsd->vending[i].value * (double)amount);
211 if( vsd->vending[vend_list[i]].amount ) {
212 if( Sql_Query( mmysql_handle, "UPDATE `%s` SET `amount` = %d WHERE `vending_id` = %d and `cartinventory_id` = %d", vending_items_table, vsd->vending[vend_list[i]].amount, vsd->vender_id, vsd->cart.u.items_cart[idx].id ) != SQL_SUCCESS ) {
213 Sql_ShowDebug( mmysql_handle );
216 if( Sql_Query( mmysql_handle, "DELETE FROM `%s` WHERE `vending_id` = %d and `cartinventory_id` = %d", vending_items_table, vsd->vender_id, vsd->cart.u.items_cart[idx].id ) != SQL_SUCCESS ) {
217 Sql_ShowDebug( mmysql_handle );
221 pc_cart_delitem(vsd, idx, amount, 0, LOG_TYPE_VENDING);
222 if( battle_config.vending_tax )
223 z -= z * (battle_config.vending_tax/10000.);
224 clif_vendingreport(vsd, idx, amount, sd->status.char_id, (int)z);
227 if( battle_config.buyer_name ) {
229 sprintf(temp, msg_txt(sd,265), sd->status.name);
230 clif_messagecolor(&vsd->bl, color_table[COLOR_LIGHT_GREEN], temp, false, SELF);
234 // compact the vending list
235 for( i = 0, cursor = 0; i < vsd->vend_num; i++ ) {
236 if( vsd->vending[i].amount == 0 )
239 if( cursor != i ) { // speedup
240 vsd->vending[cursor].index = vsd->vending[i].index;
241 vsd->vending[cursor].amount = vsd->vending[i].amount;
242 vsd->vending[cursor].value = vsd->vending[i].value;
248 vsd->vend_num = cursor;
250 //Always save BOTH: customer (buyer) and vender
251 if( save_settings&CHARSAVE_VENDING ) {
252 chrif_save(sd, CSAVE_INVENTORY|CSAVE_CART);
253 chrif_save(vsd, CSAVE_INVENTORY|CSAVE_CART);
256 //check for @AUTOTRADE users [durf]
257 if( vsd->state.autotrade ) {
258 //see if there is anything left in the shop
259 ARR_FIND( 0, vsd->vend_num, i, vsd->vending[i].amount > 0 );
260 if( i == vsd->vend_num ) {
261 //Close Vending (this was automatically done by the client, we have to do it manually for autovenders) [Skotlex]
262 vending_closevending(vsd);
263 map_quit(vsd); //They have no reason to stay around anymore, do they?
269 * Player setup a new shop
270 * @param sd : player opening the shop
271 * @param message : shop title
272 * @param data : itemlist data
273 * data := {<index>.w <amount>.w <value>.l}[count]
274 * @param count : number of different items
275 * @param at Autotrader info, or NULL if requetsed not from autotrade persistance
276 * @return 0 If success, 1 - Cannot open (die, not state.prevend, trading), 2 - No cart, 3 - Count issue, 4 - Cart data isn't saved yet, 5 - No valid item found
278 int8 vending_openvending(struct map_session_data* sd, const char* message, const uint8* data, int count, struct s_autotrader *at)
281 int vending_skill_lvl;
282 char message_sql[MESSAGE_SIZE*2];
285 nullpo_retr(false,sd);
287 if ( pc_isdead(sd) || !sd->state.prevend || pc_istrading(sd)) {
288 return 1; // can't open vendings lying dead || didn't use via the skill (wpe/hack) || can't have 2 shops at once
291 vending_skill_lvl = pc_checkskill(sd, MC_VENDING);
293 // skill level and cart check
294 if( !vending_skill_lvl || !pc_iscarton(sd) ) {
295 clif_skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0);
299 // check number of items in shop
300 if( count < 1 || count > MAX_VENDING || count > 2 + vending_skill_lvl ) { // invalid item count
301 clif_skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0);
305 if (save_settings&CHARSAVE_VENDING) // Avoid invalid data from saving
306 chrif_save(sd, CSAVE_INVENTORY|CSAVE_CART);
308 // filter out invalid items
310 for( j = 0; j < count; j++ ) {
311 short index = *(uint16*)(data + 8*j + 0);
312 short amount = *(uint16*)(data + 8*j + 2);
313 unsigned int value = *(uint32*)(data + 8*j + 4);
315 index -= 2; // offset adjustment (client says that the first cart position is 2)
317 if( index < 0 || index >= MAX_CART // invalid position
318 || pc_cartitem_amount(sd, index, amount) < 0 // invalid item or insufficient quantity
319 //NOTE: official server does not do any of the following checks!
320 || !sd->cart.u.items_cart[index].identify // unidentified item
321 || sd->cart.u.items_cart[index].attribute == 1 // broken item
322 || sd->cart.u.items_cart[index].expire_time // It should not be in the cart but just in case
323 || (sd->cart.u.items_cart[index].bound && !pc_can_give_bounded_items(sd)) // can't trade account bound items and has no permission
324 || !itemdb_cantrade(&sd->cart.u.items_cart[index], pc_get_group_level(sd), pc_get_group_level(sd)) ) // untradeable item
327 sd->vending[i].index = index;
328 sd->vending[i].amount = amount;
329 sd->vending[i].value = min(value, (unsigned int)battle_config.vending_max_value);
330 i++; // item successfully added
334 clif_displaymessage (sd->fd, msg_txt(sd,266)); //"Some of your items cannot be vended and were removed from the shop."
336 if( i == 0 ) { // no valid item found
337 clif_skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0); // custom reply packet
341 sd->state.prevend = 0;
342 sd->state.vending = true;
343 sd->state.workinprogress = WIP_DISABLE_NONE;
344 sd->vender_id = vending_getuid();
346 safestrncpy(sd->message, message, MESSAGE_SIZE);
348 Sql_EscapeString( mmysql_handle, message_sql, sd->message );
350 if( Sql_Query( mmysql_handle, "INSERT INTO `%s`(`id`, `account_id`, `char_id`, `sex`, `map`, `x`, `y`, `title`, `autotrade`, `body_direction`, `head_direction`, `sit`) "
351 "VALUES( %d, %d, %d, '%c', '%s', %d, %d, '%s', %d, '%d', '%d', '%d' );",
352 vendings_table, sd->vender_id, sd->status.account_id, sd->status.char_id, sd->status.sex == SEX_FEMALE ? 'F' : 'M', map[sd->bl.m].name, sd->bl.x, sd->bl.y, message_sql, sd->state.autotrade, at ? at->dir : sd->ud.dir, at ? at->head_dir : sd->head_dir, at ? at->sit : pc_issit(sd) ) != SQL_SUCCESS ) {
353 Sql_ShowDebug(mmysql_handle);
356 StringBuf_Init(&buf);
357 StringBuf_Printf(&buf, "INSERT INTO `%s`(`vending_id`,`index`,`cartinventory_id`,`amount`,`price`) VALUES", vending_items_table);
358 for (j = 0; j < i; j++) {
359 StringBuf_Printf(&buf, "(%d,%d,%d,%d,%d)", sd->vender_id, j, sd->cart.u.items_cart[sd->vending[j].index].id, sd->vending[j].amount, sd->vending[j].value);
361 StringBuf_AppendStr(&buf, ",");
363 if (SQL_ERROR == Sql_QueryStr(mmysql_handle, StringBuf_Value(&buf)))
364 Sql_ShowDebug(mmysql_handle);
365 StringBuf_Destroy(&buf);
367 clif_openvending(sd,sd->bl.id,sd->vending);
368 clif_showvendingboard(&sd->bl,message,0);
370 idb_put(vending_db, sd->status.char_id, sd);
376 * Checks if an item is being sold in given player's vending.
377 * @param sd : vender session (player)
378 * @param nameid : item id
379 * @return 0:not selling it, 1: yes
381 bool vending_search(struct map_session_data* sd, unsigned short nameid)
385 if( !sd->state.vending ) { // not vending
389 ARR_FIND( 0, sd->vend_num, i, sd->cart.u.items_cart[sd->vending[i].index].nameid == (short)nameid );
390 if( i == sd->vend_num ) { // not found
398 * Searches for all items in a vending, that match given ids, price and possible cards.
399 * @param sd : The vender session to search into
400 * @param s : parameter of the search (see s_search_store_search)
401 * @return Whether or not the search should be continued.
403 bool vending_searchall(struct map_session_data* sd, const struct s_search_store_search* s)
406 unsigned int idx, cidx;
409 if( !sd->state.vending ) // not vending
412 for( idx = 0; idx < s->item_count; idx++ ) {
413 ARR_FIND( 0, sd->vend_num, i, sd->cart.u.items_cart[sd->vending[i].index].nameid == (short)s->itemlist[idx] );
414 if( i == sd->vend_num ) { // not found
417 it = &sd->cart.u.items_cart[sd->vending[i].index];
419 if( s->min_price && s->min_price > sd->vending[i].value ) { // too low price
423 if( s->max_price && s->max_price < sd->vending[i].value ) { // too high price
427 if( s->card_count ) { // check cards
428 if( itemdb_isspecial(it->card[0]) ) { // something, that is not a carded
431 slot = itemdb_slot(it->nameid);
433 for( c = 0; c < slot && it->card[c]; c ++ ) {
434 ARR_FIND( 0, s->card_count, cidx, s->cardlist[cidx] == it->card[c] );
435 if( cidx != s->card_count ) { // found
440 if( c == slot || !it->card[c] ) { // no card match
445 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
454 * Open vending for Autotrader
455 * @param sd Player as autotrader
457 void vending_reopen( struct map_session_data* sd )
459 struct s_autotrader *at = NULL;
464 // Open vending for this autotrader
465 if ((at = (struct s_autotrader *)uidb_get(vending_autotrader_db, sd->status.char_id)) && at->count && at->entries) {
469 // Init vending data for autotrader
470 CREATE(data, uint8, at->count * 8);
472 for (j = 0, p = data, count = at->count; j < at->count; j++) {
473 struct s_autotrade_entry *entry = at->entries[j];
474 uint16 *index = (uint16*)(p + 0);
475 uint16 *amount = (uint16*)(p + 2);
476 uint32 *value = (uint32*)(p + 4);
478 // Find item position in cart
479 ARR_FIND(0, MAX_CART, entry->index, sd->cart.u.items_cart[entry->index].id == entry->cartinventory_id);
481 if (entry->index == MAX_CART) {
486 *index = entry->index + 2;
487 *amount = itemdb_isstackable(sd->cart.u.items_cart[entry->index].nameid) ? entry->amount : 1;
488 *value = entry->price;
493 sd->state.prevend = 1; // Set him into a hacked prevend state
494 sd->state.autotrade = 1;
496 // Make sure abort all NPCs
497 npc_event_dequeue(sd);
498 pc_cleareventtimer(sd);
500 // Open the vending again
501 if( (fail = vending_openvending(sd, at->title, data, count, at)) == 0 ) {
502 // Make vendor look perfect
503 pc_setdir(sd, at->dir, at->head_dir);
504 clif_changed_dir(&sd->bl, AREA_WOS);
508 clif_sitting(&sd->bl);
512 chrif_save(sd, CSAVE_AUTOTRADE);
514 ShowInfo("Vending loaded for '"CL_WHITE"%s"CL_RESET"' with '"CL_WHITE"%d"CL_RESET"' items at "CL_WHITE"%s (%d,%d)"CL_RESET"\n",
515 sd->status.name, count, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y);
521 vending_autotrader_remove(at, true);
522 if (db_size(vending_autotrader_db) == 0)
523 vending_autotrader_db->clear(vending_autotrader_db, vending_autotrader_free);
527 ShowError("vending_reopen: (Error:%d) Load failed for autotrader '"CL_WHITE"%s"CL_RESET"' (CID=%d/AID=%d)\n", fail, sd->status.name, sd->status.char_id, sd->status.account_id);
533 * Initializing autotraders from table
535 void do_init_vending_autotrade(void)
537 if (battle_config.feature_autotrade) {
538 if (Sql_Query(mmysql_handle,
539 "SELECT `id`, `account_id`, `char_id`, `sex`, `title`, `body_direction`, `head_direction`, `sit` "
541 "WHERE `autotrade` = 1 AND (SELECT COUNT(`vending_id`) FROM `%s` WHERE `vending_id` = `id`) > 0 "
543 vendings_table, vending_items_table ) != SQL_SUCCESS )
545 Sql_ShowDebug(mmysql_handle);
549 if( Sql_NumRows(mmysql_handle) > 0 ) {
551 DBIterator *iter = NULL;
552 struct s_autotrader *at = NULL;
554 // Init each autotrader data
555 while (SQL_SUCCESS == Sql_NextRow(mmysql_handle)) {
560 CREATE(at, struct s_autotrader, 1);
561 Sql_GetData(mmysql_handle, 0, &data, NULL); at->id = atoi(data);
562 Sql_GetData(mmysql_handle, 1, &data, NULL); at->account_id = atoi(data);
563 Sql_GetData(mmysql_handle, 2, &data, NULL); at->char_id = atoi(data);
564 Sql_GetData(mmysql_handle, 3, &data, NULL); at->sex = (data[0] == 'F') ? SEX_FEMALE : SEX_MALE;
565 Sql_GetData(mmysql_handle, 4, &data, &len); safestrncpy(at->title, data, zmin(len + 1, MESSAGE_SIZE));
566 Sql_GetData(mmysql_handle, 5, &data, NULL); at->dir = atoi(data);
567 Sql_GetData(mmysql_handle, 6, &data, NULL); at->head_dir = atoi(data);
568 Sql_GetData(mmysql_handle, 7, &data, NULL); at->sit = atoi(data);
571 if (battle_config.feature_autotrade_direction >= 0)
572 at->dir = battle_config.feature_autotrade_direction;
573 if (battle_config.feature_autotrade_head_direction >= 0)
574 at->head_dir = battle_config.feature_autotrade_head_direction;
575 if (battle_config.feature_autotrade_sit >= 0)
576 at->sit = battle_config.feature_autotrade_sit;
579 CREATE(at->sd, struct map_session_data, 1);
580 pc_setnewpc(at->sd, at->account_id, at->char_id, 0, gettick(), at->sex, 0);
581 at->sd->state.autotrade = 1|2;
582 at->sd->state.monster_ignore = (battle_config.autotrade_monsterignore);
583 chrif_authreq(at->sd, true);
584 uidb_put(vending_autotrader_db, at->char_id, at);
586 Sql_FreeResult(mmysql_handle);
588 // Init items for each autotraders
589 iter = db_iterator(vending_autotrader_db);
590 for (at = (struct s_autotrader *)dbi_first(iter); dbi_exists(iter); at = (struct s_autotrader *)dbi_next(iter)) {
593 if (SQL_ERROR == Sql_Query(mmysql_handle,
594 "SELECT `cartinventory_id`, `amount`, `price` "
596 "WHERE `vending_id` = %d "
597 "ORDER BY `index` ASC;",
598 vending_items_table, at->id ) )
600 Sql_ShowDebug(mmysql_handle);
604 if (!(at->count = (uint16)Sql_NumRows(mmysql_handle))) {
606 vending_autotrader_remove(at, true);
611 CREATE(at->entries, struct s_autotrade_entry *, at->count);
613 //Add the item into list
615 while (SQL_SUCCESS == Sql_NextRow(mmysql_handle) && j < at->count) {
617 CREATE(at->entries[j], struct s_autotrade_entry, 1);
618 Sql_GetData(mmysql_handle, 0, &data, NULL); at->entries[j]->cartinventory_id = atoi(data);
619 Sql_GetData(mmysql_handle, 1, &data, NULL); at->entries[j]->amount = atoi(data);
620 Sql_GetData(mmysql_handle, 2, &data, NULL); at->entries[j]->price = atoi(data);
624 Sql_FreeResult(mmysql_handle);
628 ShowStatus("Done loading '"CL_WHITE"%d"CL_RESET"' vending autotraders with '"CL_WHITE"%d"CL_RESET"' items.\n", db_size(vending_autotrader_db), items);
632 // Everything is loaded fine, their entries will be reinserted once they are loaded
633 if (Sql_Query( mmysql_handle, "DELETE FROM `%s`;", vendings_table ) != SQL_SUCCESS ||
634 Sql_Query( mmysql_handle, "DELETE FROM `%s`;", vending_items_table ) != SQL_SUCCESS) {
635 Sql_ShowDebug(mmysql_handle);
640 * Remove an autotrader's data
641 * @param at Autotrader
642 * @param remove If true will removes from vending_autotrader_db
644 static void vending_autotrader_remove(struct s_autotrader *at, bool remove) {
646 if (at->count && at->entries) {
648 for (i = 0; i < at->count; i++) {
650 aFree(at->entries[i]);
655 uidb_remove(vending_autotrader_db, at->char_id);
660 * Clear all autotraders
663 static int vending_autotrader_free(DBKey key, DBData *data, va_list ap) {
664 struct s_autotrader *at = (struct s_autotrader *)db_data2ptr(data);
666 vending_autotrader_remove(at, false);
671 * Initialise the vending module
672 * called in map::do_init
674 void do_final_vending(void)
676 db_destroy(vending_db);
677 vending_autotrader_db->destroy(vending_autotrader_db, vending_autotrader_free);
681 * Destory the vending module
682 * called in map::do_final
684 void do_init_vending(void)
686 vending_db = idb_alloc(DB_OPT_BASE);
687 vending_autotrader_db = uidb_alloc(DB_OPT_BASE);