Pārlūkot izejas kodu

Merge branch 'cleanup/autotrade_persistance' of https://github.com/rathena/rathena

aleos89 10 gadi atpakaļ
vecāks
revīzija
6ae8404e4e
7 mainītis faili ar 225 papildinājumiem un 265 dzēšanām
  1. 91 126
      src/map/buyingstore.c
  2. 30 2
      src/map/buyingstore.h
  3. 2 2
      src/map/clif.c
  4. 4 2
      src/map/pc.c
  5. 1 1
      src/map/pc.h
  6. 96 131
      src/map/vending.c
  7. 1 1
      src/map/vending.h

+ 91 - 126
src/map/buyingstore.c

@@ -17,32 +17,10 @@
 
 #include <stdlib.h> // 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 +62,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 +106,14 @@ char buyingstore_setup(struct map_session_data* sd, unsigned char slots){
 * @param storename
 * @param *itemlist { <nameid>.W, <amount>.W, <price>.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 +226,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 +580,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 +606,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,18 +627,22 @@ 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 (at) {
+		buyingstore_autotrader_remove(at, true);
+		if (db_size(buyingstore_autotrader_db) == 0)
+			buyingstore_autotrader_db->clear(buyingstore_autotrader_db, buyingstore_autotrader_free);
+	}
 
-		//If the last autotrade is loaded, clear autotraders [Cydh]
-		if (++autotrader_loaded_count >= autotrader_count)
-			do_final_buyingstore_autotrade();
+	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);
 	}
 }
 
@@ -674,11 +651,6 @@ void buyingstore_reopen( struct map_session_data* sd ){
 */
 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 +662,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 +695,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 +720,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 +730,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 +740,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);
 		}
 	}
 
@@ -793,32 +755,34 @@ void do_init_buyingstore_autotrade( void ) {
 }
 
 /**
-* Clear all autotraders
-* @author [Cydh]
-*/
-void do_final_buyingstore_autotrade(void) {
-	if (autotrader_count && autotraders){
+ * 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;
-		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++;
+		for (i = 0; i < at->count; i++) {
+			if (at->entries[i])
+				aFree(at->entries[i]);
 		}
-		aFree(autotraders);
-		autotrader_count = 0;
+		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 = db_data2ptr(data);
+	if (at)
+		buyingstore_autotrader_remove(at, false);
+	return 0;
 }
 
 /**
@@ -827,7 +791,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 +800,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;
 }

+ 30 - 2
src/map/buyingstore.h

@@ -22,8 +22,36 @@ 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);
+/// Autotrade entry
+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;
+};
+
+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);

+ 2 - 2
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);
 }
 
 

+ 4 - 2
src/map/pc.c

@@ -10905,8 +10905,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;

+ 1 - 1
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;

+ 96 - 131
src/map/vending.c

@@ -12,39 +12,17 @@
 #include "chrif.h"
 #include "vending.h"
 #include "pc.h"
+#include "buyingstore.h" // struct s_autotrade_entry, struct s_autotrader
 
 #include <stdlib.h> // 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 +266,15 @@ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const ui
  * @param data : itemlist data
  *	data := {<index>.w <amount>.w <value>.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 +352,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 +461,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 +495,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,20 +516,21 @@ 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 (at) {
+		vending_autotrader_remove(at, true);
+		if (db_size(vending_autotrader_db) == 0)
+			vending_autotrader_db->clear(vending_autotrader_db, vending_autotrader_free);
+	}
 
-		//If the last autotrade is loaded, clear autotraders [Cydh]
-		if (++autotrader_loaded_count >= autotrader_count)
-			do_final_vending_autotrade();
+	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);
 	}
 }
 
@@ -564,42 +540,30 @@ void vending_reopen( struct map_session_data* sd )
 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);
 		}
 	}
 
@@ -679,33 +642,34 @@ void do_init_vending_autotrade(void)
 }
 
 /**
-* Clear all autotraders
-* @author [Cydh]
-*/
-void do_final_vending_autotrade(void)
-{
-	if (autotrader_count && autotraders) {
+ * 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;
-		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++;
+		for (i = 0; i < at->count; i++) {
+			if (at->entries[i])
+				aFree(at->entries[i]);
 		}
-		aFree(autotraders);
-		autotrader_count = 0;
+		aFree(at->entries);
 	}
+	if (remove)
+		uidb_remove(vending_autotrader_db, at->char_id);
+	aFree(at);
+}
+
+/**
+* Clear all autotraders
+* @author [Cydh]
+*/
+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;
 }

+ 1 - 1
src/map/vending.h

@@ -22,7 +22,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);