diff --git a/src/map/buyingstore.c b/src/map/buyingstore.c index c60224a36a..827315d9ea 100644 --- a/src/map/buyingstore.c +++ b/src/map/buyingstore.c @@ -14,35 +14,14 @@ #include "log.h" // log_pick_pc, log_zeny #include "pc.h" // struct map_session_data #include "chrif.h" +#include "vending.h" // struct s_autotrade_entry, struct s_autotrader #include // atoi -/// Struct for buyingstore entry of autotrader -struct s_autotrade_entry { - uint16 amount; - int price; - unsigned short item_id; -}; - -/// Struct of autotrader -struct s_autotrade { - uint32 account_id; - uint32 char_id; - int buyer_id; - int m; - uint16 x, y; - unsigned char sex, dir, head_dir, sit; - char title[MESSAGE_SIZE]; - int limit; - uint16 count; - struct s_autotrade_entry **entries; - struct map_session_data *sd; -}; - //Autotrader -static struct s_autotrade **autotraders; ///Autotraders Storage -static uint16 autotrader_count, autotrader_loaded_count; ///Autotrader count -static void do_final_buyingstore_autotrade(void); +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 @@ -84,7 +63,7 @@ static unsigned int buyingstore_getuid(void) * @param slots Number of item on the list * @return 0 If success, 1 - Cannot open, 2 - Manner penalty, 3 - Mapflag restiction, 4 - Cell restriction */ -char buyingstore_setup(struct map_session_data* sd, unsigned char slots){ +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) { @@ -128,12 +107,14 @@ char buyingstore_setup(struct map_session_data* sd, unsigned char slots){ * @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 */ -char buyingstore_create(struct map_session_data* sd, int zenylimit, unsigned char result, const char* storename, const uint8* itemlist, unsigned int count) +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); @@ -246,17 +227,22 @@ char buyingstore_create(struct map_session_data* sd, int zenylimit, unsigned cha 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`) " + 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_db, sd->buyer_id, sd->status.account_id, sd->status.char_id, sd->status.sex == 0 ? 'F' : 'M', map[sd->bl.m].name, sd->bl.x, sd->bl.y, message_sql, sd->buyingstore.zenylimit, sd->state.autotrade, sd->ud.dir, sd->head_dir, pc_issit(sd) ) != SQL_SUCCESS ){ + buyingstores_db, sd->buyer_id, sd->status.account_id, sd->status.char_id, sd->status.sex == 0 ? 'F' : 'M', map[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); } - for( i = 0; i < sd->buyingstore.slots; i++ ){ - if( Sql_Query( mmysql_handle, "INSERT INTO `%s`(`buyingstore_id`,`index`,`item_id`,`amount`,`price`) VALUES( %d, %d, %hu, %d, %d );", buyingstore_items_db, sd->buyer_id, i, sd->buyingstore.items[i].nameid, sd->buyingstore.items[i].amount, sd->buyingstore.items[i].price ) != 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_db); + 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); @@ -595,24 +581,21 @@ bool buyingstore_searchall(struct map_session_data* sd, const struct s_search_st * @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 ( autotrader_count > 0 && autotraders){ - uint16 i; - uint8 *data, *p, fail = 0; + if ((at = uidb_get(buyingstore_autotrader_db, sd->status.char_id)) && at->count && at->entries) { + uint8 *data, *p; uint16 j, count; - ARR_FIND(0,autotrader_count,i,autotraders[i] && autotraders[i]->char_id == sd->status.char_id); - if (i >= autotrader_count) { - return; - } - // Init buyingstore data for autotrader - CREATE(data, uint8, autotraders[i]->count * 8); + CREATE(data, uint8, at->count * 8); - for (j = 0, p = data, count = autotraders[i]->count; j < autotraders[i]->count; j++) { - struct s_autotrade_entry *entry = autotraders[i]->entries[j]; + 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); @@ -624,29 +607,20 @@ void buyingstore_reopen( struct map_session_data* sd ){ 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)autotraders[i]->count )) == 0 && - (fail = buyingstore_create( sd, autotraders[i]->limit, 1, autotraders[i]->title, data, autotraders[i]->count )) == 0 ) + if( (fail = buyingstore_setup( sd, (unsigned char)at->count )) == 0 && + (fail = buyingstore_create( sd, at->limit, 1, at->title, data, at->count, at )) == 0 ) { - ShowInfo("Loaded buyingstore 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); - - // Set him to autotrade - if (Sql_Query( mmysql_handle, "UPDATE `%s` SET `autotrade` = 1, `body_direction` = '%d', `head_direction` = '%d', `sit` = '%d' " - "WHERE `id` = %d;", - buyingstores_db, autotraders[i]->dir, autotraders[i]->head_dir, autotraders[i]->sit, sd->buyer_id ) != SQL_SUCCESS ) - { - Sql_ShowDebug( mmysql_handle ); - } - // Make buyer look perfect - pc_setdir(sd, autotraders[i]->dir, autotraders[i]->head_dir); + pc_setdir(sd, at->dir, at->head_dir); clif_changed_dir(&sd->bl, AREA_WOS); - if( autotraders[i]->sit ) { + if( at->sit ) { pc_setsit(sd); skill_sit(sd, 1); clif_sitting(&sd->bl); @@ -654,31 +628,31 @@ void buyingstore_reopen( struct map_session_data* sd ){ // Immediate save chrif_save(sd, 3); - }else{ - // Failed to open the buyingstore, set him offline - ShowError("Failed (%d) to load autotrade buyingstore data for '"CL_WHITE"%s"CL_RESET"' with '"CL_WHITE"%d"CL_RESET"' items\n", fail, sd->status.name, count ); - map_quit( sd ); + 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 the last autotrade is loaded, clear autotraders [Cydh] - if (++autotrader_loaded_count >= autotrader_count) - do_final_buyingstore_autotrade(); + 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 +* TODO: Make this support for multi map-server */ void do_init_buyingstore_autotrade( void ) { if(battle_config.feature_autotrade) { - uint16 i, items = 0; - autotrader_count = autotrader_loaded_count = 0; - - // Get autotrader from table. `map`, `x`, and `y`, aren't used here - // Just read player that has data at buyingstore_items [Cydh] if (Sql_Query(mmysql_handle, "SELECT `id`, `account_id`, `char_id`, `sex`, `title`, `limit`, `body_direction`, `head_direction`, `sit` " "FROM `%s` " @@ -690,27 +664,19 @@ void do_init_buyingstore_autotrade( void ) { return; } - if( (autotrader_count = (uint16)Sql_NumRows(mmysql_handle)) > 0 ){ - // Init autotraders - CREATE(autotraders, struct s_autotrade *, autotrader_count); - - if (autotraders == NULL) { //This is shouldn't happen [Cydh] - ShowError("Failed to initialize buyingstore autotraders!\n"); - Sql_FreeResult(mmysql_handle); - return; - } + if( Sql_NumRows(mmysql_handle) > 0 ) { + uint16 items = 0; + DBIterator *iter = NULL; + struct s_autotrader *at = NULL; // Init each autotrader data - i = 0; - while (SQL_SUCCESS == Sql_NextRow(mmysql_handle) && i < autotrader_count) { + while (SQL_SUCCESS == Sql_NextRow(mmysql_handle)) { size_t len; char* data; - struct s_autotrade *at = NULL; - CREATE(autotraders[i], struct s_autotrade, 1); - at = autotraders[i]; - - Sql_GetData(mmysql_handle, 0, &data, NULL); at->buyer_id = atoi(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; @@ -731,27 +697,24 @@ void do_init_buyingstore_autotrade( void ) { // 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; + at->sd->state.autotrade = 1|4; at->sd->state.monster_ignore = (battle_config.autotrade_monsterignore); chrif_authreq(at->sd, true); - i++; + uidb_put(buyingstore_autotrader_db, at->char_id, at); } Sql_FreeResult(mmysql_handle); - - //Init items on buying list each autotrader - for (i = 0; i < autotrader_count; i++){ - struct s_autotrade *at = NULL; - uint16 j; - - if ((at = autotraders[i]) == NULL) - continue; + + // Init items for each autotraders + iter = db_iterator(buyingstore_autotrader_db); + for (at = dbi_first(iter); dbi_exists(iter); at = 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_db, at->buyer_id ) ) + buyingstore_items_db, at->id ) ) { Sql_ShowDebug(mmysql_handle); continue; @@ -759,6 +722,7 @@ void do_init_buyingstore_autotrade( void ) { if (!(at->count = (uint16)Sql_NumRows(mmysql_handle))) { map_quit(at->sd); + buyingstore_autotrader_remove(at, true); continue; } @@ -768,9 +732,8 @@ void do_init_buyingstore_autotrade( void ) { //Add the item into list j = 0; while (SQL_SUCCESS == Sql_NextRow(mmysql_handle) && j < at->count) { - char* data; + 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); @@ -779,8 +742,9 @@ void do_init_buyingstore_autotrade( void ) { 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", autotrader_count, items); + ShowStatus("Done loading '"CL_WHITE"%d"CL_RESET"' buyingstore autotraders with '"CL_WHITE"%d"CL_RESET"' items.\n", db_size(buyingstore_autotrader_db), items); } } @@ -792,33 +756,35 @@ void do_init_buyingstore_autotrade( void ) { } } +/** + * 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] */ -void do_final_buyingstore_autotrade(void) { - if (autotrader_count && autotraders){ - uint16 i = 0; - while (i < autotrader_count) { //Free the autotrader - if (autotraders[i] == NULL) - continue; - if (autotraders[i]->count) { - uint16 j = 0; - while (j < autotraders[i]->count) { //Free the autotrade entries - if (autotraders[i]->entries == NULL) - continue; - if (autotraders[i]->entries[j]) - aFree(autotraders[i]->entries[j]); - j++; - } - aFree(autotraders[i]->entries); - } - aFree(autotraders[i]); - i++; - } - aFree(autotraders); - autotrader_count = 0; - } +static int buyingstore_autotrader_free(DBKey key, DBData *data, va_list ap) { + struct s_autotrader *at = db_data2ptr(data); + if (at) + buyingstore_autotrader_remove(at, false); + return 0; } /** @@ -827,7 +793,7 @@ void do_final_buyingstore_autotrade(void) { */ void do_final_buyingstore(void) { db_destroy(buyingstore_db); - do_final_buyingstore_autotrade(); + buyingstore_autotrader_db->destroy(buyingstore_autotrader_db, buyingstore_autotrader_free); } /** @@ -836,5 +802,6 @@ void do_final_buyingstore(void) { */ void do_init_buyingstore(void) { buyingstore_db = idb_alloc(DB_OPT_BASE); + buyingstore_autotrader_db = uidb_alloc(DB_OPT_BASE); buyingstore_nextid = 0; } diff --git a/src/map/buyingstore.h b/src/map/buyingstore.h index cdebb4d1e2..820cd106be 100644 --- a/src/map/buyingstore.h +++ b/src/map/buyingstore.h @@ -22,8 +22,8 @@ struct s_buyingstore unsigned char slots; }; -char buyingstore_setup(struct map_session_data* sd, unsigned char slots); -char buyingstore_create(struct map_session_data* sd, int zenylimit, unsigned char result, const char* storename, const uint8* itemlist, unsigned int count); +int8 buyingstore_setup(struct map_session_data* sd, unsigned char slots); +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); void buyingstore_close(struct map_session_data* sd); void buyingstore_open(struct map_session_data* sd, uint32 account_id); void buyingstore_trade(struct map_session_data* sd, uint32 account_id, unsigned int buyer_id, const uint8* itemlist, unsigned int count); diff --git a/src/map/clif.c b/src/map/clif.c index 9c39efde6d..8bd92dc5c7 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -12513,7 +12513,7 @@ void clif_parse_OpenVending(int fd, struct map_session_data* sd){ if( message[0] == '\0' ) // invalid input return; - vending_openvending(sd, message, data, len/8); + vending_openvending(sd, message, data, len/8, NULL); } @@ -16235,7 +16235,7 @@ static void clif_parse_ReqOpenBuyingStore(int fd, struct map_session_data* sd) } count = packet_len/blocksize; - buyingstore_create(sd, zenylimit, result, storename, itemlist, count); + buyingstore_create(sd, zenylimit, result, storename, itemlist, count, NULL); } diff --git a/src/map/pc.c b/src/map/pc.c index 9e81d88abf..d4ff7d31f1 100755 --- a/src/map/pc.c +++ b/src/map/pc.c @@ -10899,8 +10899,10 @@ int pc_autotrade_timer(int tid, unsigned int tick, int id, intptr_t data) { sd->autotrade_tid = INVALID_TIMER; - buyingstore_reopen(sd); - vending_reopen(sd); + if (sd->state.autotrade&2) + vending_reopen(sd); + if (sd->state.autotrade&4) + buyingstore_reopen(sd); if (!sd->vender_id && !sd->buyer_id) { sd->state.autotrade = 0; diff --git a/src/map/pc.h b/src/map/pc.h index 43dbf09cfd..8d537a576d 100644 --- a/src/map/pc.h +++ b/src/map/pc.h @@ -190,7 +190,7 @@ struct map_session_data { unsigned int snovice_dead_flag : 1; //Explosion spirits on death: 0 off, 1 used. unsigned int abra_flag : 2; // Abracadabra bugfix by Aru unsigned int autocast : 1; // Autospell flag [Inkfish] - unsigned int autotrade : 1; //By Fantik + unsigned int autotrade : 3; //By Fantik. &2 Requested by vending autotrade; &4 Requested by buyingstore autotrade unsigned int reg_dirty : 4; //By Skotlex (marks whether registry variables have been saved or not yet) unsigned int showdelay :1; unsigned int showexp :1; diff --git a/src/map/vending.c b/src/map/vending.c index d8e2f6a31d..f60970e4f5 100755 --- a/src/map/vending.c +++ b/src/map/vending.c @@ -15,36 +15,13 @@ #include // atoi -/// Struct for vending entry of autotrader -struct s_autotrade_entry { - int cartinventory_id; - uint16 amount; - int price; - uint16 index; -}; - -/// Struct of autotrader -struct s_autotrade { - uint32 account_id; - uint32 char_id; - int vendor_id; - int m; - uint16 x, - y; - unsigned char sex, dir, head_dir, sit; - char title[MESSAGE_SIZE]; - uint16 count; - struct s_autotrade_entry **entries; - struct map_session_data *sd; -}; - -static int vending_nextid = 0; ///Vending_id counter -static DBMap *vending_db; ///Db holder the vender : charid -> map_session_data +static uint32 vending_nextid = 0; ///Vending_id counter +static DBMap *vending_db; ///DB holder the vender : charid -> map_session_data //Autotrader -static struct s_autotrade **autotraders; ///Autotraders Storage -static uint16 autotrader_count, autotrader_loaded_count; ///Autotrader count -static void do_final_vending_autotrade(void); +static DBMap *vending_autotrader_db; /// Holds autotrader info: char_id -> struct s_autotrader +static void vending_autotrader_remove(struct s_autotrader *at, bool remove); +static int vending_autotrader_free(DBKey key, DBData *data, va_list ap); /** * Lookup to get the vending_db outside module @@ -288,13 +265,15 @@ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const ui * @param data : itemlist data * data := {.w .w .l}[count] * @param count : number of different items + * @param at Autotrader info, or NULL if requetsed not from autotrade persistance * @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 */ -char vending_openvending(struct map_session_data* sd, const char* message, const uint8* data, int count) +int8 vending_openvending(struct map_session_data* sd, const char* message, const uint8* data, int count, struct s_autotrader *at) { int i, j; int vending_skill_lvl; char message_sql[MESSAGE_SIZE*2]; + StringBuf buf; nullpo_retr(false,sd); @@ -372,17 +351,22 @@ char vending_openvending(struct map_session_data* sd, const char* message, const 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`,`autotrade`, `body_direction`, `head_direction`, `sit`) " + if( Sql_Query( mmysql_handle, "INSERT INTO `%s`(`id`, `account_id`, `char_id`, `sex`, `map`, `x`, `y`, `title`, `autotrade`, `body_direction`, `head_direction`, `sit`) " "VALUES( %d, %d, %d, '%c', '%s', %d, %d, '%s', %d, '%d', '%d', '%d' );", - vendings_db, sd->vender_id, sd->status.account_id, sd->status.char_id, sd->status.sex == 0 ? 'F' : 'M', map[sd->bl.m].name, sd->bl.x, sd->bl.y, message_sql, sd->state.autotrade, sd->ud.dir, sd->head_dir, pc_issit(sd) ) != SQL_SUCCESS ) { + vendings_db, sd->vender_id, sd->status.account_id, sd->status.char_id, sd->status.sex == 0 ? '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 ) { Sql_ShowDebug(mmysql_handle); } - for( i = 0; i < count; i++ ) { - if( Sql_Query( mmysql_handle, "INSERT INTO `%s`(`vending_id`,`index`,`cartinventory_id`,`amount`,`price`) VALUES( %d, %d, %d, %d, %d );", vending_items_db, sd->vender_id, i, sd->status.cart[sd->vending[i].index].id, sd->vending[i].amount, sd->vending[i].value ) != SQL_SUCCESS ) { - Sql_ShowDebug(mmysql_handle); - } + StringBuf_Init(&buf); + StringBuf_Printf(&buf, "INSERT INTO `%s`(`vending_id`,`index`,`cartinventory_id`,`amount`,`price`) VALUES", vending_items_db); + for (i = 0; i < count; i++) { + StringBuf_Printf(&buf, "(%d,%d,%d,%d,%d)", sd->vender_id, i, sd->status.cart[sd->vending[i].index].id, sd->vending[i].amount, sd->vending[i].value); + if (i < count-1) + StringBuf_AppendStr(&buf, ","); } + if (SQL_ERROR == Sql_QueryStr(mmysql_handle, StringBuf_Value(&buf))) + Sql_ShowDebug(mmysql_handle); + StringBuf_Destroy(&buf); clif_openvending(sd,sd->bl.id,sd->vending); clif_showvendingboard(&sd->bl,message,0); @@ -476,24 +460,21 @@ bool vending_searchall(struct map_session_data* sd, const struct s_search_store_ */ void vending_reopen( struct map_session_data* sd ) { + struct s_autotrader *at = NULL; + int8 fail = -1; + nullpo_retv(sd); - // Ready to open vending for this char - if ( autotrader_count > 0 && autotraders) { - uint16 i; - uint8 *data, *p, fail = 0; + // Open vending for this autotrader + if ((at = uidb_get(vending_autotrader_db, sd->status.char_id)) && at->count && at->entries) { + uint8 *data, *p; uint16 j, count; - ARR_FIND(0,autotrader_count,i,autotraders[i] && autotraders[i]->char_id == sd->status.char_id); - if (i >= autotrader_count) { - return; - } - // Init vending data for autotrader - CREATE(data, uint8, autotraders[i]->count * 8); + CREATE(data, uint8, at->count * 8); - for (j = 0, p = data, count = autotraders[i]->count; j < autotraders[i]->count; j++) { - struct s_autotrade_entry *entry = autotraders[i]->entries[j]; + for (j = 0, p = data, count = at->count; j < at->count; j++) { + struct s_autotrade_entry *entry = at->entries[j]; uint16 *index = (uint16*)(p + 0); uint16 *amount = (uint16*)(p + 2); uint32 *value = (uint32*)(p + 4); @@ -513,26 +494,19 @@ void vending_reopen( struct map_session_data* sd ) p += 8; } - // Set him into a hacked prevend state - sd->state.prevend = 1; + sd->state.prevend = 1; // Set him into a hacked prevend state + sd->state.autotrade = 1; // Make sure abort all NPCs npc_event_dequeue(sd); pc_cleareventtimer(sd); // Open the vending again - if( (fail = vending_openvending(sd, autotraders[i]->title, data, count)) == 0 ) { - // Set him to autotrade - if (Sql_Query( mmysql_handle, "UPDATE `%s` SET `autotrade` = 1, `body_direction` = '%d', `head_direction` = '%d', `sit` = '%d' " - "WHERE `id` = %d;", - vendings_db, autotraders[i]->dir, autotraders[i]->head_dir, autotraders[i]->sit, sd->vender_id ) != SQL_SUCCESS ) { - Sql_ShowDebug( mmysql_handle ); - } - + if( (fail = vending_openvending(sd, at->title, data, count, at)) == 0 ) { // Make vendor look perfect - pc_setdir(sd, autotraders[i]->dir, autotraders[i]->head_dir); + pc_setdir(sd, at->dir, at->head_dir); clif_changed_dir(&sd->bl, AREA_WOS); - if( autotraders[i]->sit ) { + if( at->sit ) { pc_setsit(sd); skill_sit(sd, 1); clif_sitting(&sd->bl); @@ -541,65 +515,55 @@ void vending_reopen( struct map_session_data* sd ) // Immediate save chrif_save(sd, 3); - ShowInfo("Loaded vending for '"CL_WHITE"%s"CL_RESET"' with '"CL_WHITE"%d"CL_RESET"' items at "CL_WHITE"%s (%d,%d)"CL_RESET"\n", + 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", sd->status.name, count, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y); - } else { - // Failed to open the vending, set him offline - ShowError("Failed (%d) to load autotrade vending data for '"CL_WHITE"%s"CL_RESET"' with '"CL_WHITE"%d"CL_RESET"' items\n", fail, sd->status.name, count ); - - map_quit( sd ); } - aFree(data); + } - //If the last autotrade is loaded, clear autotraders [Cydh] - if (++autotrader_loaded_count >= autotrader_count) - do_final_vending_autotrade(); + if (at) { + vending_autotrader_remove(at, true); + if (db_size(vending_autotrader_db) == 0) + vending_autotrader_db->clear(vending_autotrader_db, vending_autotrader_free); + } + + if (fail != 0) { + ShowError("vending_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 +* TODO: Make this support for multi map-server */ void do_init_vending_autotrade(void) { if (battle_config.feature_autotrade) { - uint16 i, items = 0; - autotrader_count = autotrader_loaded_count = 0; - - // Get autotrader from table. `map`, `x`, and `y`, aren't used here - // Just read player that has data at vending_items [Cydh] if (Sql_Query(mmysql_handle, "SELECT `id`, `account_id`, `char_id`, `sex`, `title`, `body_direction`, `head_direction`, `sit` " "FROM `%s` " "WHERE `autotrade` = 1 AND (SELECT COUNT(`vending_id`) FROM `%s` WHERE `vending_id` = `id`) > 0 " "ORDER BY `id`;", - vendings_db, vending_items_db ) != SQL_SUCCESS ) { + vendings_db, vending_items_db ) != SQL_SUCCESS ) + { Sql_ShowDebug(mmysql_handle); return; } - if( (autotrader_count = (uint16)Sql_NumRows(mmysql_handle)) > 0 ) { - // Init autotraders - CREATE(autotraders, struct s_autotrade *, autotrader_count); - - if (autotraders == NULL) { //This is shouldn't happen [Cydh] - ShowError("Failed to initialize vending autotraders!\n"); - Sql_FreeResult(mmysql_handle); - return; - } + if( Sql_NumRows(mmysql_handle) > 0 ) { + uint16 items = 0; + DBIterator *iter = NULL; + struct s_autotrader *at = NULL; // Init each autotrader data - i = 0; - while (SQL_SUCCESS == Sql_NextRow(mmysql_handle) && i < autotrader_count) { + while (SQL_SUCCESS == Sql_NextRow(mmysql_handle)) { size_t len; char* data; - struct s_autotrade *at = NULL; - CREATE(autotraders[i], struct s_autotrade, 1); - at = autotraders[i]; - - Sql_GetData(mmysql_handle, 0, &data, NULL); at->vendor_id = atoi(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; @@ -619,45 +583,43 @@ void do_init_vending_autotrade(void) // 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; + at->sd->state.autotrade = 1|2; at->sd->state.monster_ignore = (battle_config.autotrade_monsterignore); chrif_authreq(at->sd, true); - i++; + uidb_put(vending_autotrader_db, at->char_id, at); } Sql_FreeResult(mmysql_handle); - //Init items on vending list each autotrader - for (i = 0; i < autotrader_count; i++) { - struct s_autotrade *at = NULL; - uint16 j; - - if ((at = autotraders[i]) == NULL) - continue; + // Init items for each autotraders + iter = db_iterator(vending_autotrader_db); + for (at = dbi_first(iter); dbi_exists(iter); at = dbi_next(iter)) { + uint16 j = 0; if (SQL_ERROR == Sql_Query(mmysql_handle, "SELECT `cartinventory_id`, `amount`, `price` " "FROM `%s` " "WHERE `vending_id` = %d " "ORDER BY `index` ASC;", - vending_items_db, at->vendor_id ) ) { + vending_items_db, at->id ) ) + { Sql_ShowDebug(mmysql_handle); continue; } if (!(at->count = (uint16)Sql_NumRows(mmysql_handle))) { map_quit(at->sd); + vending_autotrader_remove(at, true); continue; } - + //Init the list - CREATE(at->entries, struct s_autotrade_entry *,at->count); + 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; + char *data; CREATE(at->entries[j], struct s_autotrade_entry, 1); - Sql_GetData(mmysql_handle, 0, &data, NULL); at->entries[j]->cartinventory_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); @@ -666,8 +628,9 @@ void do_init_vending_autotrade(void) items += j; Sql_FreeResult(mmysql_handle); } + dbi_destroy(iter); - ShowStatus("Done loading '"CL_WHITE"%d"CL_RESET"' vending autotraders with '"CL_WHITE"%d"CL_RESET"' items.\n", autotrader_count, items); + ShowStatus("Done loading '"CL_WHITE"%d"CL_RESET"' vending autotraders with '"CL_WHITE"%d"CL_RESET"' items.\n", db_size(vending_autotrader_db), items); } } @@ -678,34 +641,35 @@ void do_init_vending_autotrade(void) } } +/** + * Remove an autotrader's data + * @param at Autotrader + * @param remove If true will removes from vending_autotrader_db + **/ +static void vending_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(vending_autotrader_db, at->char_id); + aFree(at); +} + /** * Clear all autotraders * @author [Cydh] */ -void do_final_vending_autotrade(void) -{ - if (autotrader_count && autotraders) { - uint16 i = 0; - while (i < autotrader_count) { //Free the autotrader - if (autotraders[i] == NULL) - continue; - if (autotraders[i]->count) { - uint16 j = 0; - while (j < autotraders[i]->count) { //Free the autotrade entries - if (autotraders[i]->entries == NULL) - continue; - if (autotraders[i]->entries[j]) - aFree(autotraders[i]->entries[j]); - j++; - } - aFree(autotraders[i]->entries); - } - aFree(autotraders[i]); - i++; - } - aFree(autotraders); - autotrader_count = 0; - } +static int vending_autotrader_free(DBKey key, DBData *data, va_list ap) { + struct s_autotrader *at = db_data2ptr(data); + if (at) + vending_autotrader_remove(at, false); + return 0; } /** @@ -715,7 +679,7 @@ void do_final_vending_autotrade(void) void do_final_vending(void) { db_destroy(vending_db); - do_final_vending_autotrade(); //Make sure everything is cleared [Cydh] + vending_autotrader_db->destroy(vending_autotrader_db, vending_autotrader_free); } /** @@ -725,5 +689,6 @@ void do_final_vending(void) void do_init_vending(void) { vending_db = idb_alloc(DB_OPT_BASE); + vending_autotrader_db = uidb_alloc(DB_OPT_BASE); vending_nextid = 0; } diff --git a/src/map/vending.h b/src/map/vending.h index a449684a51..e6ef7fabde 100644 --- a/src/map/vending.h +++ b/src/map/vending.h @@ -15,6 +15,34 @@ struct s_vending { unsigned int value; ///at wich price }; +/// Struct for vending entry of autotrader +struct s_autotrade_entry { + uint16 cartinventory_id; ///< Item entry id/cartinventory_id in cart_inventory table (for vending) + uint16 amount; ///< Amount + uint32 price; ///< Price + uint16 index; ///< Item index in cart + uint32 item_id; ///< Item ID (for buyingstore) +}; + +/// Struct of autotrader +struct s_autotrader { + uint16 id; ///< vendor/buyer id + uint32 account_id; ///< Account ID + uint32 char_id; ///< Char ID + int m; ///< Map location + uint16 x, ///< X location + y; ///< Y location + unsigned char sex, ///< Autotrader's sex + dir, ///< Body direction + head_dir, ///< Head direction + sit; ///< Is sitting? + char title[MESSAGE_SIZE]; ///< Store name + uint32 limit; ///< Maximum zeny to be spent (for buyingstore) + uint16 count; ///< Number of item in store + struct s_autotrade_entry **entries; ///< Store details + struct map_session_data *sd; +}; + DBMap * vending_getdb(); void do_final_vending(void); void do_init_vending(void); @@ -22,7 +50,7 @@ void do_init_vending_autotrade( void ); void vending_reopen( struct map_session_data* sd ); void vending_closevending(struct map_session_data* sd); -char vending_openvending(struct map_session_data* sd, const char* message, const uint8* data, int count); +int8 vending_openvending(struct map_session_data* sd, const char* message, const uint8* data, int count, struct s_autotrader *at); 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);