// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL // For more information, see LICENCE in the main folder #include "buyingstore.hpp" // struct s_buyingstore #include // atoi #include "../common/db.hpp" // ARR_FIND #include "../common/malloc.hpp" // aMalloc, aFree #include "../common/nullpo.hpp" #include "../common/showmsg.hpp" // ShowWarning #include "../common/socket.hpp" // RBUF* #include "../common/strlib.hpp" // safestrncpy #include "../common/timer.hpp" // gettick #include "atcommand.hpp" // msg_txt #include "battle.hpp" // battle_config.* #include "chrif.hpp" #include "clif.hpp" // clif_buyingstore_* #include "log.hpp" // log_pick_pc, log_zeny #include "npc.hpp" #include "pc.hpp" // struct map_session_data //Autotrader static DBMap *buyingstore_autotrader_db; /// Holds autotrader info: char_id -> struct s_autotrader static void buyingstore_autotrader_remove(struct s_autotrader *at, bool remove); static int buyingstore_autotrader_free(DBKey key, DBData *data, va_list ap); /// constants (client-side restrictions) #define BUYINGSTORE_MAX_PRICE 99990000 #define BUYINGSTORE_MAX_AMOUNT 9999 static DBMap *buyingstore_db; DBMap *buyingstore_getdb(void) { return buyingstore_db; } /// failure constants for clif functions enum e_buyingstore_failure { BUYINGSTORE_CREATE = 1, // "Failed to open buying store." BUYINGSTORE_CREATE_OVERWEIGHT = 2, // "Total amount of then possessed items exceeds the weight limit by %d. Please re-enter." BUYINGSTORE_TRADE_BUYER_ZENY = 3, // "All items within the buy limit were purchased." BUYINGSTORE_TRADE_BUYER_NO_ITEMS = 4, // "All items were purchased." BUYINGSTORE_TRADE_SELLER_FAILED = 5, // "The deal has failed." BUYINGSTORE_TRADE_SELLER_COUNT = 6, // "The trade failed, because the entered amount of item %s is higher, than the buyer is willing to buy." BUYINGSTORE_TRADE_SELLER_ZENY = 7, // "The trade failed, because the buyer is lacking required balance." BUYINGSTORE_CREATE_NO_INFO = 8, // "No sale (purchase) information available." }; static unsigned int buyingstore_nextid = 0; static const unsigned short buyingstore_blankslots[MAX_SLOTS] = { 0 }; // used when checking whether or not an item's card slots are blank /// Returns unique buying store id static unsigned int buyingstore_getuid(void) { return ++buyingstore_nextid; } /** * Attempt to setup buying store fast check before create new one * @param sd * @param slots Number of item on the list * @return 0 If success, 1 - Cannot open, 2 - Manner penalty, 3 - Mapflag restiction, 4 - Cell restriction */ int8 buyingstore_setup(struct map_session_data* sd, unsigned char slots){ nullpo_retr(1, sd); if (!battle_config.feature_buying_store || sd->state.vending || sd->state.buyingstore || sd->state.trading || slots == 0) { return 1; } if( sd->sc.data[SC_NOCHAT] && (sd->sc.data[SC_NOCHAT]->val1&MANNER_NOROOM) ) {// custom: mute limitation return 2; } if( map_getmapflag(sd->bl.m, MF_NOVENDING) ) {// custom: no vending maps clif_displaymessage(sd->fd, msg_txt(sd,276)); // "You can't open a shop on this map" return 3; } if( map_getcell(sd->bl.m, sd->bl.x, sd->bl.y, CELL_CHKNOVENDING) ) {// custom: no vending cells clif_displaymessage(sd->fd, msg_txt(sd,204)); // "You can't open a shop on this cell." return 4; } if( slots > MAX_BUYINGSTORE_SLOTS ) { ShowWarning("buyingstore_setup: Requested %d slots, but server supports only %d slots.\n", (int)slots, MAX_BUYINGSTORE_SLOTS); slots = MAX_BUYINGSTORE_SLOTS; } sd->buyingstore.slots = slots; clif_buyingstore_open(sd); return 0; } /** * Attempt to create new buying store * @param sd * @param zenylimit * @param result * @param storename * @param *itemlist { .W, .W, .L }* * @param count Number of item on the itemlist * @param at Autotrader info, or NULL if requetsed not from autotrade persistance * @return 0 If success, 1 - Cannot open, 2 - Manner penalty, 3 - Mapflag restiction, 4 - Cell restriction, 5 - Invalid count/result, 6 - Cannot give item, 7 - Will be overweight */ int8 buyingstore_create(struct map_session_data* sd, int zenylimit, unsigned char result, const char* storename, const uint8* itemlist, unsigned int count, struct s_autotrader *at) { unsigned int i, weight, listidx; char message_sql[MESSAGE_SIZE*2]; StringBuf buf; nullpo_retr(1, sd); if( !result || count == 0 ) {// canceled, or no items return 5; } if( !battle_config.feature_buying_store || pc_istrading(sd) || sd->buyingstore.slots == 0 || count > sd->buyingstore.slots || zenylimit <= 0 || zenylimit > sd->status.zeny || !storename[0] ) {// disabled or invalid input sd->buyingstore.slots = 0; clif_buyingstore_open_failed(sd, BUYINGSTORE_CREATE, 0); return 1; } if( !pc_can_give_items(sd) ) {// custom: GM is not allowed to buy (give zeny) sd->buyingstore.slots = 0; clif_displaymessage(sd->fd, msg_txt(sd,246)); clif_buyingstore_open_failed(sd, BUYINGSTORE_CREATE, 0); return 6; } if( sd->sc.data[SC_NOCHAT] && (sd->sc.data[SC_NOCHAT]->val1&MANNER_NOROOM) ) {// custom: mute limitation return 2; } if( map_getmapflag(sd->bl.m, MF_NOVENDING) ) {// custom: no vending maps clif_displaymessage(sd->fd, msg_txt(sd,276)); // "You can't open a shop on this map" return 3; } if( map_getcell(sd->bl.m, sd->bl.x, sd->bl.y, CELL_CHKNOVENDING) ) {// custom: no vending cells clif_displaymessage(sd->fd, msg_txt(sd,204)); // "You can't open a shop on this cell." return 4; } weight = sd->weight; // check item list for( i = 0; i < count; i++ ) {// itemlist: .W .W .L unsigned short nameid, amount; int price, idx; struct item_data* id; nameid = RBUFW(itemlist,i*8+0); amount = RBUFW(itemlist,i*8+2); price = RBUFL(itemlist,i*8+4); if( ( id = itemdb_exists(nameid) ) == NULL || amount == 0 ) {// invalid input break; } if( price <= 0 || price > BUYINGSTORE_MAX_PRICE ) {// invalid price: unlike vending, items cannot be bought at 0 Zeny break; } if( !id->flag.buyingstore || !itemdb_cantrade_sub(id, pc_get_group_level(sd), pc_get_group_level(sd)) || ( idx = pc_search_inventory(sd, nameid) ) == -1 ) {// restrictions: allowed, no character-bound items and at least one must be owned break; } if( sd->inventory.u.items_inventory[idx].amount + amount > BUYINGSTORE_MAX_AMOUNT ) {// too many items of same kind break; } if( i ) {// duplicate check. as the client does this too, only malicious intent should be caught here ARR_FIND( 0, i, listidx, sd->buyingstore.items[listidx].nameid == nameid ); if( listidx != i ) {// duplicate ShowWarning("buyingstore_create: Found duplicate item on buying list (nameid=%hu, amount=%hu, account_id=%d, char_id=%d).\n", nameid, amount, sd->status.account_id, sd->status.char_id); break; } } weight+= id->weight*amount; sd->buyingstore.items[i].nameid = nameid; sd->buyingstore.items[i].amount = amount; sd->buyingstore.items[i].price = price; } if( i != count ) {// invalid item/amount/price sd->buyingstore.slots = 0; clif_buyingstore_open_failed(sd, BUYINGSTORE_CREATE, 0); return 5; } if( (sd->max_weight*90)/100 < weight ) {// not able to carry all wanted items without getting overweight (90%) sd->buyingstore.slots = 0; clif_buyingstore_open_failed(sd, BUYINGSTORE_CREATE_OVERWEIGHT, weight); return 7; } // success sd->state.buyingstore = true; sd->buyer_id = buyingstore_getuid(); sd->buyingstore.zenylimit = zenylimit; sd->buyingstore.slots = i; // store actual amount of items safestrncpy(sd->message, storename, sizeof(sd->message)); Sql_EscapeString( mmysql_handle, message_sql, sd->message ); if( Sql_Query( mmysql_handle, "INSERT INTO `%s`(`id`, `account_id`, `char_id`, `sex`, `map`, `x`, `y`, `title`, `limit`, `autotrade`, `body_direction`, `head_direction`, `sit`) " "VALUES( %d, %d, %d, '%c', '%s', %d, %d, '%s', %d, %d, '%d', '%d', '%d' );", buyingstores_table, sd->buyer_id, sd->status.account_id, sd->status.char_id, sd->status.sex == 0 ? 'F' : 'M', map_getmapdata(sd->bl.m)->name, sd->bl.x, sd->bl.y, message_sql, sd->buyingstore.zenylimit, sd->state.autotrade, at ? at->dir : sd->ud.dir, at ? at->head_dir : sd->head_dir, at ? at->sit : pc_issit(sd) ) != SQL_SUCCESS ){ Sql_ShowDebug(mmysql_handle); } StringBuf_Init(&buf); StringBuf_Printf(&buf, "INSERT INTO `%s`(`buyingstore_id`,`index`,`item_id`,`amount`,`price`) VALUES", buyingstore_items_table); for (i = 0; i < sd->buyingstore.slots; i++){ StringBuf_Printf(&buf, "(%d,%d,%hu,%d,%d)", sd->buyer_id, i, sd->buyingstore.items[i].nameid, sd->buyingstore.items[i].amount, sd->buyingstore.items[i].price); if (i < sd->buyingstore.slots-1) StringBuf_AppendStr(&buf, ","); } if (SQL_ERROR == Sql_QueryStr(mmysql_handle, StringBuf_Value(&buf))) Sql_ShowDebug(mmysql_handle); StringBuf_Destroy(&buf); clif_buyingstore_myitemlist(sd); clif_buyingstore_entry(sd); idb_put(buyingstore_db, sd->status.char_id, sd); return 0; } /** * Close buying store and clear buying store data from tables * @param sd */ void buyingstore_close(struct map_session_data* sd) { nullpo_retv(sd); if( sd->state.buyingstore ) { if( Sql_Query( mmysql_handle, "DELETE FROM `%s` WHERE buyingstore_id = %d;", buyingstore_items_table, sd->buyer_id ) != SQL_SUCCESS || Sql_Query( mmysql_handle, "DELETE FROM `%s` WHERE `id` = %d;", buyingstores_table, sd->buyer_id ) != SQL_SUCCESS ) { Sql_ShowDebug(mmysql_handle); } sd->state.buyingstore = false; sd->buyer_id = 0; memset(&sd->buyingstore, 0, sizeof(sd->buyingstore)); idb_remove(buyingstore_db, sd->status.char_id); // notify other players clif_buyingstore_disappear_entry(sd); } } /** * Open buying store from buyer * @param sd Player * @param account_id Buyer account ID */ void buyingstore_open(struct map_session_data* sd, uint32 account_id) { struct map_session_data* pl_sd; nullpo_retv(sd); if( !battle_config.feature_buying_store || pc_istrading(sd) ) {// not allowed to sell return; } if( !pc_can_give_items(sd) ) {// custom: GM is not allowed to sell clif_displaymessage(sd->fd, msg_txt(sd,246)); return; } if( ( pl_sd = map_id2sd(account_id) ) == NULL || !pl_sd->state.buyingstore ) {// not online or not buying return; } 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; } // success clif_buyingstore_itemlist(sd, pl_sd); } /** * Start transaction * @param sd Player/Seller * @param account_id Buyer account ID * @param *itemlist List of sold items { .W, .W, .W }* * @param count Number of item on the itemlist */ void buyingstore_trade(struct map_session_data* sd, uint32 account_id, unsigned int buyer_id, const uint8* itemlist, unsigned int count) { int zeny = 0; unsigned int i, weight, listidx, k; struct map_session_data* pl_sd; nullpo_retv(sd); if( count == 0 ) {// nothing to do return; } if( !battle_config.feature_buying_store || pc_istrading(sd) ) {// not allowed to sell clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0); return; } if( !pc_can_give_items(sd) ) {// custom: GM is not allowed to sell clif_displaymessage(sd->fd, msg_txt(sd,246)); clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0); return; } if( ( pl_sd = map_id2sd(account_id) ) == NULL || !pl_sd->state.buyingstore || pl_sd->buyer_id != buyer_id ) {// not online, not buying or not same store clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0); return; } 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; // check item list for( i = 0; i < count; i++ ) {// itemlist: .W .W .W unsigned short nameid, amount; int index; index = RBUFW(itemlist,i*6+0)-2; nameid = RBUFW(itemlist,i*6+2); amount = RBUFW(itemlist,i*6+4); if( i ) {// duplicate check. as the client does this too, only malicious intent should be caught here ARR_FIND( 0, i, k, RBUFW(itemlist,k*6+0)-2 == index ); if( k != i ) {// duplicate ShowWarning("buyingstore_trade: Found duplicate item on selling list (prevnameid=%hu, prevamount=%hu, nameid=%hu, amount=%hu, account_id=%d, char_id=%d).\n", RBUFW(itemlist,k*6+2), RBUFW(itemlist,k*6+4), nameid, amount, sd->status.account_id, sd->status.char_id); clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); return; } } if( index < 0 || index >= ARRAYLENGTH(sd->inventory.u.items_inventory) || sd->inventory_data[index] == NULL || sd->inventory.u.items_inventory[index].nameid != nameid || sd->inventory.u.items_inventory[index].amount < amount ) {// invalid input clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); return; } if( sd->inventory.u.items_inventory[index].expire_time || (sd->inventory.u.items_inventory[index].bound && !pc_can_give_bounded_items(sd)) || !itemdb_cantrade(&sd->inventory.u.items_inventory[index], pc_get_group_level(sd), pc_get_group_level(pl_sd)) || memcmp(sd->inventory.u.items_inventory[index].card, buyingstore_blankslots, sizeof(buyingstore_blankslots)) ) {// non-tradable item clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); return; } ARR_FIND( 0, pl_sd->buyingstore.slots, listidx, pl_sd->buyingstore.items[listidx].nameid == nameid ); if( listidx == pl_sd->buyingstore.slots || pl_sd->buyingstore.items[listidx].amount == 0 ) {// there is no such item or the buyer has already bought all of them clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); return; } if( pl_sd->buyingstore.items[listidx].amount < amount ) {// buyer does not need that much of the item clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_COUNT, nameid); return; } if( pc_checkadditem(pl_sd, nameid, amount) == CHKADDITEM_OVERAMOUNT ) {// buyer does not have enough space for this item clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); return; } if( amount*(unsigned int)sd->inventory_data[index]->weight > pl_sd->max_weight-weight ) {// normally this is not supposed to happen, as the total weight is // checked upon creation, but the buyer could have gained items clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); return; } weight+= amount*sd->inventory_data[index]->weight; if( amount*pl_sd->buyingstore.items[listidx].price > pl_sd->buyingstore.zenylimit-zeny ) {// buyer does not have enough zeny clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_ZENY, nameid); return; } zeny+= amount*pl_sd->buyingstore.items[listidx].price; } // process item list for( i = 0; i < count; i++ ) {// itemlist: .W .W .W unsigned short nameid, amount; int index; index = RBUFW(itemlist,i*6+0)-2; nameid = RBUFW(itemlist,i*6+2); amount = RBUFW(itemlist,i*6+4); ARR_FIND( 0, pl_sd->buyingstore.slots, listidx, pl_sd->buyingstore.items[listidx].nameid == nameid ); zeny = amount*pl_sd->buyingstore.items[listidx].price; // move item pc_additem(pl_sd, &sd->inventory.u.items_inventory[index], amount, LOG_TYPE_BUYING_STORE); pc_delitem(sd, index, amount, 1, 0, LOG_TYPE_BUYING_STORE); pl_sd->buyingstore.items[listidx].amount-= amount; if( pl_sd->buyingstore.items[listidx].amount > 0 ){ if( Sql_Query( mmysql_handle, "UPDATE `%s` SET `amount` = %d WHERE `buyingstore_id` = %d AND `index` = %d;", buyingstore_items_table, pl_sd->buyingstore.items[listidx].amount, pl_sd->buyer_id, listidx ) != SQL_SUCCESS ){ Sql_ShowDebug( mmysql_handle ); } }else{ if( Sql_Query( mmysql_handle, "DELETE FROM `%s` WHERE `buyingstore_id` = %d AND `index` = %d;", buyingstore_items_table, pl_sd->buyer_id, listidx ) != SQL_SUCCESS ){ Sql_ShowDebug( mmysql_handle ); } } // pay up pc_payzeny(pl_sd, zeny, LOG_TYPE_BUYING_STORE, sd); pc_getzeny(sd, zeny, LOG_TYPE_BUYING_STORE, pl_sd); pl_sd->buyingstore.zenylimit-= zeny; // notify clients clif_buyingstore_delete_item(sd, index, amount, pl_sd->buyingstore.items[listidx].price); clif_buyingstore_update_item(pl_sd, nameid, amount, sd->status.char_id, zeny); } if( save_settings&CHARSAVE_VENDING ) { chrif_save(sd, CSAVE_NORMAL|CSAVE_INVENTORY); chrif_save(pl_sd, CSAVE_NORMAL|CSAVE_INVENTORY); } // check whether or not there is still something to buy ARR_FIND( 0, pl_sd->buyingstore.slots, i, pl_sd->buyingstore.items[i].amount != 0 ); if( i == pl_sd->buyingstore.slots ) {// everything was bought clif_buyingstore_trade_failed_buyer(pl_sd, BUYINGSTORE_TRADE_BUYER_NO_ITEMS); } else if( pl_sd->buyingstore.zenylimit == 0 ) {// zeny limit reached clif_buyingstore_trade_failed_buyer(pl_sd, BUYINGSTORE_TRADE_BUYER_ZENY); } else {// continue buying if( Sql_Query( mmysql_handle, "UPDATE `%s` SET `limit` = %d WHERE `id` = %d;", buyingstores_table, pl_sd->buyingstore.zenylimit, pl_sd->buyer_id ) != SQL_SUCCESS ){ Sql_ShowDebug( mmysql_handle ); } return; } // cannot continue buying buyingstore_close(pl_sd); // remove auto-trader if( pl_sd->state.autotrade ) { 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; nullpo_ret(sd); 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; nullpo_ret(sd); 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; } /** * Open buyingstore for Autotrader * @param sd Player as autotrader */ void buyingstore_reopen( struct map_session_data* sd ){ struct s_autotrader *at = NULL; int8 fail = -1; nullpo_retv(sd); // Ready to open buyingstore for this char if ((at = (struct s_autotrader *)uidb_get(buyingstore_autotrader_db, sd->status.char_id)) && at->count && at->entries) { uint8 *data, *p; uint16 j, count; // Init buyingstore data for autotrader CREATE(data, uint8, at->count * 8); for (j = 0, p = data, count = at->count; j < at->count; j++) { struct s_autotrade_entry *entry = at->entries[j]; unsigned short *item_id = (uint16*)(p + 0); uint16 *amount = (uint16*)(p + 2); uint32 *price = (uint32*)(p + 4); *item_id = entry->item_id; *amount = entry->amount; *price = entry->price; p += 8; } sd->state.autotrade = 1; // Make sure abort all NPCs npc_event_dequeue(sd); pc_cleareventtimer(sd); // Open the buyingstore again if( (fail = buyingstore_setup( sd, (unsigned char)at->count )) == 0 && (fail = buyingstore_create( sd, at->limit, 1, at->title, data, at->count, at )) == 0 ) { // Make buyer look perfect pc_setdir(sd, at->dir, at->head_dir); clif_changed_dir(&sd->bl, AREA_WOS); if( at->sit ) { pc_setsit(sd); skill_sit(sd, 1); clif_sitting(&sd->bl); } // Immediate save chrif_save(sd, CSAVE_AUTOTRADE); ShowInfo("Buyingstore loaded for '" CL_WHITE "%s" CL_RESET "' with '" CL_WHITE "%d" CL_RESET "' items at " CL_WHITE "%s (%d,%d)" CL_RESET "\n", sd->status.name, count, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y); } aFree(data); } if (at) { buyingstore_autotrader_remove(at, true); if (db_size(buyingstore_autotrader_db) == 0) buyingstore_autotrader_db->clear(buyingstore_autotrader_db, buyingstore_autotrader_free); } if (fail != 0) { ShowError("buyingstore_reopen: (Error:%d) Load failed for autotrader '" CL_WHITE "%s" CL_RESET "' (CID=%/AID=%d)\n", fail, sd->status.name, sd->status.char_id, sd->status.account_id); map_quit(sd); } } /** * Initializing autotraders from table */ void do_init_buyingstore_autotrade( void ) { if(battle_config.feature_autotrade) { if (Sql_Query(mmysql_handle, "SELECT `id`, `account_id`, `char_id`, `sex`, `title`, `limit`, `body_direction`, `head_direction`, `sit` " "FROM `%s` " "WHERE `autotrade` = 1 AND `limit` > 0 AND (SELECT COUNT(`buyingstore_id`) FROM `%s` WHERE `buyingstore_id` = `id`) > 0 " "ORDER BY `id`;", buyingstores_table, buyingstore_items_table ) != SQL_SUCCESS ) { Sql_ShowDebug(mmysql_handle); return; } if( Sql_NumRows(mmysql_handle) > 0 ) { uint16 items = 0; DBIterator *iter = NULL; struct s_autotrader *at = NULL; // Init each autotrader data while (SQL_SUCCESS == Sql_NextRow(mmysql_handle)) { size_t len; char* data; at = NULL; CREATE(at, struct s_autotrader, 1); Sql_GetData(mmysql_handle, 0, &data, NULL); at->id = atoi(data); Sql_GetData(mmysql_handle, 1, &data, NULL); at->account_id = atoi(data); Sql_GetData(mmysql_handle, 2, &data, NULL); at->char_id = atoi(data); Sql_GetData(mmysql_handle, 3, &data, NULL); at->sex = (data[0] == 'F') ? 0 : 1; Sql_GetData(mmysql_handle, 4, &data, &len); safestrncpy(at->title, data, zmin(len + 1, MESSAGE_SIZE)); Sql_GetData(mmysql_handle, 5, &data, NULL); at->limit = atoi(data); Sql_GetData(mmysql_handle, 6, &data, NULL); at->dir = atoi(data); Sql_GetData(mmysql_handle, 7, &data, NULL); at->head_dir = atoi(data); Sql_GetData(mmysql_handle, 8, &data, NULL); at->sit = atoi(data); at->count = 0; if (battle_config.feature_autotrade_direction >= 0) at->dir = battle_config.feature_autotrade_direction; if (battle_config.feature_autotrade_head_direction >= 0) at->head_dir = battle_config.feature_autotrade_head_direction; if (battle_config.feature_autotrade_sit >= 0) at->sit = battle_config.feature_autotrade_sit; // initialize player CREATE(at->sd, struct map_session_data, 1); pc_setnewpc(at->sd, at->account_id, at->char_id, 0, gettick(), at->sex, 0); at->sd->state.autotrade = 1|4; if (battle_config.autotrade_monsterignore) at->sd->state.block_action |= PCBLOCK_IMMUNE; else at->sd->state.block_action &= ~PCBLOCK_IMMUNE; chrif_authreq(at->sd, true); uidb_put(buyingstore_autotrader_db, at->char_id, at); } Sql_FreeResult(mmysql_handle); // Init items for each autotraders iter = db_iterator(buyingstore_autotrader_db); for (at = (struct s_autotrader *)dbi_first(iter); dbi_exists(iter); at = (struct s_autotrader *)dbi_next(iter)) { uint16 j = 0; if (SQL_ERROR == Sql_Query(mmysql_handle, "SELECT `item_id`, `amount`, `price` " "FROM `%s` " "WHERE `buyingstore_id` = %d " "ORDER BY `index` ASC;", buyingstore_items_table, at->id ) ) { Sql_ShowDebug(mmysql_handle); continue; } if (!(at->count = (uint16)Sql_NumRows(mmysql_handle))) { map_quit(at->sd); buyingstore_autotrader_remove(at, true); continue; } //Init the list CREATE(at->entries, struct s_autotrade_entry *,at->count); //Add the item into list j = 0; while (SQL_SUCCESS == Sql_NextRow(mmysql_handle) && j < at->count) { char *data; CREATE(at->entries[j], struct s_autotrade_entry, 1); Sql_GetData(mmysql_handle, 0, &data, NULL); at->entries[j]->item_id = atoi(data); Sql_GetData(mmysql_handle, 1, &data, NULL); at->entries[j]->amount = atoi(data); Sql_GetData(mmysql_handle, 2, &data, NULL); at->entries[j]->price = atoi(data); j++; } items += j; Sql_FreeResult(mmysql_handle); } dbi_destroy(iter); ShowStatus("Done loading '" CL_WHITE "%d" CL_RESET "' buyingstore autotraders with '" CL_WHITE "%d" CL_RESET "' items.\n", db_size(buyingstore_autotrader_db), items); } } // Everything is loaded fine, their entries will be reinserted once they are loaded if (Sql_Query( mmysql_handle, "DELETE FROM `%s`;", buyingstores_table ) != SQL_SUCCESS || Sql_Query( mmysql_handle, "DELETE FROM `%s`;", buyingstore_items_table ) != SQL_SUCCESS) { Sql_ShowDebug(mmysql_handle); } } /** * Remove an autotrader's data * @param at Autotrader * @param remove If true will removes from buyingstore_autotrader_db **/ static void buyingstore_autotrader_remove(struct s_autotrader *at, bool remove) { nullpo_retv(at); if (at->count && at->entries) { uint16 i = 0; for (i = 0; i < at->count; i++) { if (at->entries[i]) aFree(at->entries[i]); } aFree(at->entries); } if (remove) uidb_remove(buyingstore_autotrader_db, at->char_id); aFree(at); } /** * Clear all autotraders * @author [Cydh] */ static int buyingstore_autotrader_free(DBKey key, DBData *data, va_list ap) { struct s_autotrader *at = (struct s_autotrader *)db_data2ptr(data); if (at) buyingstore_autotrader_remove(at, false); return 0; } /** * Initialise the buyingstore module * called in map::do_init */ void do_final_buyingstore(void) { db_destroy(buyingstore_db); buyingstore_autotrader_db->destroy(buyingstore_autotrader_db, buyingstore_autotrader_free); } /** * Destory the buyingstore module * called in map::do_final */ void do_init_buyingstore(void) { buyingstore_db = idb_alloc(DB_OPT_BASE); buyingstore_autotrader_db = uidb_alloc(DB_OPT_BASE); buyingstore_nextid = 0; }