Quellcode durchsuchen

Merge pull request #40 from Lemongrass3110/master

Added autotrade persistence support.
- Players who are autotrading during a crash/shut down will be restored on start up.
- Live vendor data is stored in SQL.
- Credits to Ind and Lemongrass.
aleos89 vor 11 Jahren
Ursprung
Commit
27cbc7ff24

+ 12 - 0
conf/battle/feature.conf

@@ -22,3 +22,15 @@ feature.atcommand_suggestions: off
 // Banking (Note 1)
 // Requires: 2013-07-24aRagexe or later
 feature.banking: on
+
+// Autotrade persistency (Note 1)
+// Should vendors that used @autotrade be restored after a restart?
+feature.autotrade: on
+
+// In which direction should respawned autotraders look?
+// Possible values are from 0-7
+// Default: 4(South)
+feature.autotrade_direction: 4
+
+// Do you want your autotraders to sit? (Note 1)
+feature.autotrade_sit: yes

+ 2 - 0
conf/inter_athena.conf

@@ -122,6 +122,8 @@ mob_skill_db_re_db: mob_skill_db_re
 mob_skill_db2_db: mob_skill_db2
 //mob_skill_db2_db: mob_skill_db2_re
 mapreg_db: mapreg
+vending_db: vendings
+vending_items_db: vending_items
 
 // Use SQL item_db, mob_db and mob_skill_db for the map server? (yes/no)
 use_sql_db: no

+ 1 - 1
npc/quests/quests_13_1.txt

@@ -12014,7 +12014,7 @@ OnTouch:
 
 que_job01,6,94,0	warp	morocc#01	2,2,morocc,45,103
 que_job01,17,48,0	warp	que_job01#02	2,2,que_job01,68,92
-que_job01,68,96,0	warp	que_job01#03	2,2,que_job01,17,53;
+que_job01,68,96,0	warp	que_job01#03	2,2,que_job01,17,53
 
 que_job01,82,95,3	script	Bar Master#moc2_01	46,{
 	if (checkweight(1201,1) == 0) {

+ 1 - 1
npc/quests/quests_13_2.txt

@@ -2548,7 +2548,7 @@ OnTouch:
 	}
 }
 
-spl_in02,236,86,0	warp	terrashome_out	1,1,splendide,285,139;
+spl_in02,236,86,0	warp	terrashome_out	1,1,splendide,285,139
 
 spl_fild01,357,44,0	script	???#ep13mdf01	844,{
 	if (checkweight(1201,2) == 0) {

+ 3 - 3
npc/quests/quests_louyang.txt

@@ -2409,9 +2409,9 @@ lou_in02,192,170,0	script	Supply Stack#2	111,{
 	close;
 }
 
-louyang,129,121,0	warp	Storage Warp#1	1,1,lou_in02,203,161;
-louyang,125,121,0	warp	Storage Warp#2	1,1,lou_in02,198,161;
-lou_in02,198,159,0	warp	Storage Warp#3	1,1,louyang,124,118;
+louyang,129,121,0	warp	Storage Warp#1	1,1,lou_in02,203,161
+louyang,125,121,0	warp	Storage Warp#2	1,1,lou_in02,198,161
+lou_in02,198,159,0	warp	Storage Warp#3	1,1,louyang,124,118
 lou_in02,203,159,0	warp	Storage Warp#4	1,1,louyang,129,118
 
 // Poison King Quest :: poison_king

+ 2 - 2
npc/quests/quests_morocc.txt

@@ -3646,8 +3646,8 @@ que_ba,181,14,7	script	Researcher#bpast_2_2	865,{
 	close;
 }
 
-que_ba,183,25,0	warp	#bpast_2to3_1	1,1,que_ba,72,25;
-que_ba,183,52,0	warp	#bpast_2to3_2	1,1,que_ba,72,51;
+que_ba,183,25,0	warp	#bpast_2to3_1	1,1,que_ba,72,25
+que_ba,183,52,0	warp	#bpast_2to3_2	1,1,que_ba,72,51
 
 que_ba,102,56,0	script	#3room_barmunt	-1,3,3,{
 //OnTouch2:

+ 21 - 0
sql-files/main.sql

@@ -712,3 +712,24 @@ CREATE TABLE IF NOT EXISTS `bonus_script` (
   `type` char(1) NOT NULL DEFAULT '0',
   `icon` varchar(3) NOT NULL DEFAULT '-1'
 ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+CREATE TABLE IF NOT EXISTS `vending_items` (
+  `vending_id` int(10) unsigned NOT NULL,
+  `index` smallint(5) unsigned NOT NULL,
+  `cartinventory_id` int(10) unsigned NOT NULL,
+  `amount` smallint(5) unsigned NOT NULL,
+  `price` int(10) unsigned NOT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1;
+
+CREATE TABLE IF NOT EXISTS `vendings` (
+  `id` int(10) unsigned NOT NULL,
+  `account_id` int(11) unsigned NOT NULL,
+  `char_id` int(10) unsigned NOT NULL,
+  `sex` enum('F','M') NOT NULL DEFAULT 'M',
+  `map` varchar(20) NOT NULL,
+  `x` smallint(5) unsigned NOT NULL,
+  `y` smallint(5) unsigned NOT NULL,
+  `title` varchar(80) NOT NULL,
+  `autotrade` tinyint(4) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1;

+ 20 - 0
sql-files/upgrades/upgrade_20140114.sql

@@ -0,0 +1,20 @@
+CREATE TABLE IF NOT EXISTS `vending_items` (
+  `vending_id` int(10) unsigned NOT NULL,
+  `index` smallint(5) unsigned NOT NULL,
+  `cartinventory_id` int(10) unsigned NOT NULL,
+  `amount` smallint(5) unsigned NOT NULL,
+  `price` int(10) unsigned NOT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1;
+
+CREATE TABLE IF NOT EXISTS `vendings` (
+  `id` int(10) unsigned NOT NULL,
+  `account_id` int(11) unsigned NOT NULL,
+  `char_id` int(10) unsigned NOT NULL,
+  `sex` enum('F','M') NOT NULL DEFAULT 'M',
+  `map` varchar(20) NOT NULL,
+  `x` smallint(5) unsigned NOT NULL,
+  `y` smallint(5) unsigned NOT NULL,
+  `title` varchar(80) NOT NULL,
+  `autotrade` tinyint(4) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1;

+ 27 - 9
src/char/char.c

@@ -3308,10 +3308,6 @@ int parse_frommap(int fd)
 					WFIFOW(fd,2) = 14 + count*sizeof(struct status_change_data);
 					WFIFOW(fd,12) = count;
 					WFIFOSET(fd,WFIFOW(fd,2));
-
-					//Clear the data once loaded.
-					if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `char_id`='%d'", scdata_db, aid, cid) )
-						Sql_ShowDebug(sql_handle);
 				}
 			}
 			Sql_FreeResult(sql_handle);
@@ -3715,8 +3711,10 @@ int parse_frommap(int fd)
 			cid = RFIFOL(fd, 8);
 			count = RFIFOW(fd, 12);
 
-			if( count > 0 )
-			{
+			// Whatever comes from the mapserver, now is the time to drop previous entries
+			if( Sql_Query( sql_handle, "DELETE FROM `%s` where `account_id` = %d and `char_id` = %d;", scdata_db, aid, cid ) != SQL_SUCCESS ){
+				Sql_ShowDebug( sql_handle );
+			}else if( count > 0 ){
 				struct status_change_data data;
 				StringBuf buf;
 				int i;
@@ -3779,7 +3777,7 @@ int parse_frommap(int fd)
 		break;
 
 		case 0x2b26: // auth request from map-server
-			if (RFIFOREST(fd) < 19)
+			if (RFIFOREST(fd) < 20)
 				return 0;
 
 		{
@@ -3791,13 +3789,15 @@ int parse_frommap(int fd)
 			struct auth_node* node;
 			struct mmo_charstatus* cd;
 			struct mmo_charstatus char_dat;
+			bool autotrade = false;
 
 			account_id = RFIFOL(fd,2);
 			char_id    = RFIFOL(fd,6);
 			login_id1  = RFIFOL(fd,10);
 			sex        = RFIFOB(fd,14);
 			ip         = ntohl(RFIFOL(fd,15));
-			RFIFOSKIP(fd,19);
+			autotrade  = RFIFOB(fd,19);
+			RFIFOSKIP(fd,20);
 
 			node = (struct auth_node*)idb_get(auth_db, account_id);
 			cd = (struct mmo_charstatus*)uidb_get(char_db_,char_id);
@@ -3806,7 +3806,25 @@ int parse_frommap(int fd)
 				mmo_char_fromsql(char_id, &char_dat, true);
 				cd = (struct mmo_charstatus*)uidb_get(char_db_,char_id);
 			}
-			if( runflag == CHARSERVER_ST_RUNNING &&
+
+			if( runflag == CHARSERVER_ST_RUNNING && autotrade && cd ){
+				uint32 mmo_charstatus_len = sizeof(struct mmo_charstatus) + 25;
+				cd->sex = sex;
+
+				WFIFOHEAD(fd,mmo_charstatus_len);
+				WFIFOW(fd,0) = 0x2afd;
+				WFIFOW(fd,2) = mmo_charstatus_len;
+				WFIFOL(fd,4) = account_id;
+				WFIFOL(fd,8) = 0;
+				WFIFOL(fd,12) = 0;
+				WFIFOL(fd,16) = 0;
+				WFIFOL(fd,20) = 0;
+				WFIFOB(fd,24) = 0;
+				memcpy(WFIFOP(fd,25), cd, sizeof(struct mmo_charstatus));
+				WFIFOSET(fd, WFIFOW(fd,2));
+
+				set_char_online(id, char_id, account_id);
+			}else if( runflag == CHARSERVER_ST_RUNNING &&
 				cd != NULL &&
 				node != NULL &&
 				node->account_id == account_id &&

+ 9 - 0
src/map/atcommand.c

@@ -5653,6 +5653,13 @@ ACMD_FUNC(autotrade) {
 	}
 
 	sd->state.autotrade = 1;
+
+	if( battle_config.feature_autotrade && 
+		sd->state.vending && 
+		Sql_Query( mmysql_handle, "UPDATE `%s` SET `autotrade` = 1 WHERE `id` = %d;", "vendings", sd->vender_id ) != SQL_SUCCESS ){
+		Sql_ShowDebug( mmysql_handle );
+	}
+
 	if( battle_config.at_timeout ) {
 		int timeout = atoi(message);
 		status_change_start(NULL,&sd->bl, SC_AUTOTRADE, 10000, 0, 0, 0, 0, ((timeout > 0) ? min(timeout,battle_config.at_timeout) : battle_config.at_timeout) * 60000, 0);
@@ -5660,6 +5667,8 @@ ACMD_FUNC(autotrade) {
 
 	channel_pcquit(sd,0xF); //leave all chan
 	clif_authfail_fd(sd->fd, 15);
+	
+	chrif_save(sd,3);
 
 	return 0;
 }

+ 3 - 0
src/map/battle.c

@@ -7340,6 +7340,9 @@ static const struct _battle_data {
 	{ "discount_item_point_shop",			&battle_config.discount_item_point_shop,		0,		0,		3,				},
 	{ "update_enemy_position",				&battle_config.update_enemy_position,			0,		0,		1,				},
 	{ "devotion_rdamage",					&battle_config.devotion_rdamage,				0,		0,		100,			},
+	{ "feature.autotrade",					&battle_config.feature_autotrade,				1,		0,		1,				},
+	{ "feature.autotrade_direction",		&battle_config.feature_autotrade_direction,		4,		0,		7,				},
+	{ "feature.autotrade_sit",				&battle_config.feature_autotrade_sit,			1,		0,		1,				},
 };
 #ifndef STATS_OPT_OUT
 /**

+ 5 - 0
src/map/battle.h

@@ -520,6 +520,11 @@ extern struct Battle_Config
 	int discount_item_point_shop;
 	int update_enemy_position;
 	int devotion_rdamage;
+
+	// autotrade persistency
+	int feature_autotrade;
+	int feature_autotrade_direction;
+	int feature_autotrade_sit;
 } battle_config;
 
 void do_init_battle(void);

+ 2 - 0
src/map/channel.c

@@ -119,6 +119,8 @@ int channel_delete(struct Channel *channel) {
 int channel_join(struct Channel *channel, struct map_session_data *sd) {
 	if(!channel || !sd)
 		return -1;
+	if(sd->state.autotrade)
+		return 0; // fake success
 	if(channel_haspc(channel,sd)==1)
 		return -2;
 

+ 20 - 8
src/map/chrif.c

@@ -277,6 +277,7 @@ int chrif_isconnected(void) {
  * Saves character data.
  * Flag = 1: Character is quitting
  * Flag = 2: Character is changing map-servers
+ * Flag = 3: Character used @autotrade
  *------------------------------------------*/
 int chrif_save(struct map_session_data *sd, int flag) {
 	uint32 mmo_charstatus_len = 0;
@@ -292,7 +293,7 @@ int chrif_save(struct map_session_data *sd, int flag) {
 		chrif_save_bsdata(sd);
 		chrif_req_login_operation(sd->status.account_id, sd->status.name, 7, 0, 2, sd->status.bank_vault); //save Bank data
 	}
-		if ( !chrif_auth_logout(sd,flag == 1 ? ST_LOGOUT : ST_MAPCHANGE) )
+		if ( flag != 3 && !chrif_auth_logout(sd,flag == 1 ? ST_LOGOUT : ST_MAPCHANGE) )
 			ShowError("chrif_save: Failed to set up player %d:%d for proper quitting!\n", sd->status.account_id, sd->status.char_id);
 	}
 
@@ -562,6 +563,9 @@ void chrif_on_ready(void) {
 
 	//Re-save any guild castles that were modified in the disconnection time.
 	guild_castle_reconnect(-1, 0, 0);
+	
+	// Charserver is ready for this now
+	do_init_vending_autotrade();
 }
 
 
@@ -615,7 +619,7 @@ int chrif_skillcooldown_request(int account_id, int char_id) {
 /*==========================================
  * Request auth confirmation
  *------------------------------------------*/
-void chrif_authreq(struct map_session_data *sd) {
+void chrif_authreq(struct map_session_data *sd, bool autotrade) {
 	struct auth_node *node= chrif_search(sd->bl.id);
 
 	if( node != NULL || !chrif_isconnected() ) {
@@ -623,14 +627,15 @@ void chrif_authreq(struct map_session_data *sd) {
 		return;
 	}
 
-	WFIFOHEAD(char_fd,19);
+	WFIFOHEAD(char_fd,20);
 	WFIFOW(char_fd,0) = 0x2b26;
 	WFIFOL(char_fd,2) = sd->status.account_id;
 	WFIFOL(char_fd,6) = sd->status.char_id;
 	WFIFOL(char_fd,10) = sd->login_id1;
 	WFIFOB(char_fd,14) = sd->status.sex;
 	WFIFOL(char_fd,15) = htonl(session[sd->fd]->client_addr);
-	WFIFOSET(char_fd,19);
+	WFIFOB(char_fd,19) = autotrade;
+	WFIFOSET(char_fd,20);
 	chrif_sd_to_auth(sd, ST_LOGIN);
 }
 
@@ -1117,8 +1122,13 @@ int chrif_disconnectplayer(int fd) {
 	}
 
 	if (!sd->fd) { //No connection
-		if (sd->state.autotrade)
+		if (sd->state.autotrade){
+			if( sd->state.vending ){
+				vending_closevending(sd);
+			}
+
 			map_quit(sd); //Remove it.
+		}
 		//Else we don't remove it because the char should have a timer to remove the player because it force-quit before,
 		//and we don't want them kicking their previous instance before the 10 secs penalty time passes. [Skotlex]
 		return 0;
@@ -1286,9 +1296,6 @@ int chrif_save_scdata(struct map_session_data *sd) { //parses the sc_data of the
 		count++;
 	}
 
-	if (count == 0)
-		return 0; //Nothing to save.
-
 	WFIFOW(char_fd,12) = count;
 	WFIFOW(char_fd,2) = 14 +count*sizeof(struct status_change_data); //Total packet size
 	WFIFOSET(char_fd,WFIFOW(char_fd,2));
@@ -1364,6 +1371,11 @@ int chrif_load_scdata(int fd) {
 		status_change_start(NULL,&sd->bl, (sc_type)data->type, 10000, data->val1, data->val2, data->val3, data->val4, data->tick, 1|2|4|8);
 	}
 #endif
+
+	if( sd->state.autotrade ){
+		vending_reopen( sd );
+	}
+
 	return 0;
 }
 

+ 1 - 1
src/map/chrif.h

@@ -35,7 +35,7 @@ struct auth_node* chrif_auth_check(int account_id, int char_id, enum sd_state st
 bool chrif_auth_delete(int account_id, int char_id, enum sd_state state);
 bool chrif_auth_finished(struct map_session_data* sd);
 
-void chrif_authreq(struct map_session_data* sd);
+void chrif_authreq(struct map_session_data* sd, bool autotrade);
 void chrif_authok(int fd);
 int chrif_scdata_request(int account_id, int char_id);
 int chrif_skillcooldown_request(int account_id, int char_id);

+ 4 - 2
src/map/clif.c

@@ -9441,7 +9441,7 @@ void clif_parse_WantToConnection(int fd, struct map_session_data* sd)
 	WFIFOSET(fd,packet_len(0x283));
 #endif
 
-	chrif_authreq(sd);
+	chrif_authreq(sd,false);
 }
 
 
@@ -17857,6 +17857,7 @@ void packetdb_readdb(void)
 		packet_len(i) = packet_len_table[i];
 
 	for(f = 0; f<ARRAYLENGTH(filename); f++){
+		entries = 0;
 		sprintf(line, "%s/%s", db_path,filename[f]);
 		if( (fp=fopen(line,"r"))==NULL ){
 			if(f==0) {
@@ -18004,7 +18005,8 @@ void packetdb_readdb(void)
 
 			clif_config.packet_db_ver = j?j:MAX_PACKET_VER;
 		}
-		ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", entries, "packet_db.txt");
+		sprintf(line, "%s/%s", db_path,filename[f]);
+		ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", entries, line);
 	}
 	ShowStatus("Using default packet version: "CL_WHITE"%d"CL_RESET".\n", clif_config.packet_db_ver);
 }

+ 1 - 1
src/map/guild.c

@@ -2105,7 +2105,7 @@ void do_init_guild(void) {
 	for(i=0; i<ARRAYLENGTH(dbsubpath); i++){
 		int n1 = strlen(db_path)+strlen(dbsubpath[i])+1;
 		char* dbsubpath1 = aMalloc(n1+1);
-		safesnprintf(dbsubpath1,n1+1,"%s/%s",db_path,dbsubpath[i]);
+		safesnprintf(dbsubpath1,n1+1,"%s%s",db_path,dbsubpath[i]);
 		
 		sv_readdb(dbsubpath1, "castle_db.txt", ',', 4, 4, -1, &guild_read_castledb, i);
 		sv_readdb(dbsubpath1, "guild_skill_tree.txt", ',', 2+MAX_GUILD_SKILL_REQUIRE*2, 2+MAX_GUILD_SKILL_REQUIRE*2, -1, &guild_read_guildskill_tree_db, i); //guild skill tree [Komurka]

+ 6 - 0
src/map/map.c

@@ -81,6 +81,8 @@ char mob_db2_db[32] = "mob_db2";
 char mob_skill_db_db[32] = "mob_skill_db";
 char mob_skill_db_re_db[32] = "mob_skill_db_re";
 char mob_skill_db2_db[32] = "mob_skill_db2";
+char vendings_db[32] = "vendings";
+char vending_items_db[32] = "vending_items";
 
 // log database
 char log_db_ip[32] = "127.0.0.1";
@@ -3555,6 +3557,10 @@ int inter_config_read(char *cfgName)
 			strcpy( item_cash_db_db, w2 );
 		else if( strcmpi( w1, "item_cash_db2_db" ) == 0 )
 			strcpy( item_cash_db2_db, w2 );
+		else if( strcmpi( w1, "vending_db" ) == 0 )
+			strcpy( vendings_db, w2 );
+		else if( strcmpi( w1, "vending_items_db" ) == 0 )
+			strcpy( vending_items_db, w2 );
 		else
 		//Map Server SQL DB
 		if(strcmpi(w1,"map_server_ip")==0)

+ 2 - 0
src/map/map.h

@@ -910,6 +910,8 @@ extern char mob_db2_db[32];
 extern char mob_skill_db_db[32];
 extern char mob_skill_db_re_db[32];
 extern char mob_skill_db2_db[32];
+extern char vendings_db[32];
+extern char vending_items_db[32];
 
 void do_shutdown(void);
 

+ 2 - 2
src/map/mob.c

@@ -4558,9 +4558,9 @@ static void mob_load(void)
 		int n2 = strlen(db_path)+strlen(DBPATH)+strlen(dbsubpath[i])+1;
 		char* dbsubpath1 = aMalloc(n1+1);
 		char* dbsubpath2 = aMalloc(n2+1);
-		safesnprintf(dbsubpath1,n1+1,"%s/%s",db_path,dbsubpath[i]);
+		safesnprintf(dbsubpath1,n1+1,"%s%s",db_path,dbsubpath[i]);
 		if(i==0) safesnprintf(dbsubpath2,n2,"%s/%s%s",db_path,DBPATH,dbsubpath[i]);
-		else safesnprintf(dbsubpath2,n2,"%s/%s",db_path,dbsubpath[i]);
+		else safesnprintf(dbsubpath2,n2,"%s%s",db_path,dbsubpath[i]);
 		
 		sv_readdb(dbsubpath1, "mob_item_ratio.txt", ',', 2, 2+MAX_ITEMRATIO_MOBS, -1, &mob_readdb_itemratio, i); // must be read before mobdb
 		sv_readdb(dbsubpath1, "mob_chat_db.txt", '#', 3, 3, MAX_MOB_CHAT, &mob_parse_row_chatdb, i);

+ 6 - 2
src/map/pc.c

@@ -1328,6 +1328,10 @@ int pc_reg_received(struct map_session_data *sd)
 
 		clif_changeoption( &sd->bl );
 	}
+	
+	if( sd->state.autotrade ){
+		clif_parse_LoadEndAck(sd->fd, sd);
+	}
 
 	return 1;
 }
@@ -10224,9 +10228,9 @@ int pc_readdb(void)
 		int n2 = strlen(db_path)+strlen(DBPATH)+strlen(dbsubpath[i])+1;
 		char* dbsubpath1 = aMalloc(n1+1);
 		char* dbsubpath2 = aMalloc(n2+1);
-		safesnprintf(dbsubpath1,n1+1,"%s/%s",db_path,dbsubpath[i]);
+		safesnprintf(dbsubpath1,n1+1,"%s%s",db_path,dbsubpath[i]);
 		if(i==0) safesnprintf(dbsubpath2,n2,"%s/%s%s",db_path,DBPATH,dbsubpath[i]);
-		else safesnprintf(dbsubpath2,n2,"%s/%s",db_path,dbsubpath[i]);
+		else safesnprintf(dbsubpath2,n2,"%s%s",db_path,dbsubpath[i]);
 
 		s = pc_read_statsdb(dbsubpath2,s,i);
 #ifdef RENEWAL_ASPD

+ 2 - 2
src/map/skill.c

@@ -19229,9 +19229,9 @@ static void skill_readdb(void) {
 		int n2 = strlen(db_path)+strlen(DBPATH)+strlen(dbsubpath[i])+1;
 		char* dbsubpath1 = aMalloc(n1+1);
 		char* dbsubpath2 = aMalloc(n2+1);
-		safesnprintf(dbsubpath1,n1+1,"%s/%s",db_path,dbsubpath[i]);
+		safesnprintf(dbsubpath1,n1+1,"%s%s",db_path,dbsubpath[i]);
 		if(i==0) safesnprintf(dbsubpath2,n2,"%s/%s%s",db_path,DBPATH,dbsubpath[i]);
-		else safesnprintf(dbsubpath2,n2,"%s/%s",db_path,dbsubpath[i]);
+		else safesnprintf(dbsubpath2,n2,"%s%s",db_path,dbsubpath[i]);
 		
 		sv_readdb(dbsubpath2, "skill_db.txt"          , ',',  18, 18, MAX_SKILL_DB, skill_parse_row_skilldb, i);
 		sv_readdb(dbsubpath2, "skill_require_db.txt"  , ',',  34, 34, MAX_SKILL_DB, skill_parse_row_requiredb, i);

+ 2 - 2
src/map/status.c

@@ -12346,9 +12346,9 @@ int status_readdb(void)
 		int n2 = strlen(db_path)+strlen(DBPATH)+strlen(dbsubpath[i])+1;
 		char* dbsubpath1 = aMalloc(n1+1);
 		char* dbsubpath2 = aMalloc(n2+1);
-		safesnprintf(dbsubpath1,n1+1,"%s/%s",db_path,dbsubpath[i]);
+		safesnprintf(dbsubpath1,n1+1,"%s%s",db_path,dbsubpath[i]);
 		if(i==0) safesnprintf(dbsubpath2,n2,"%s/%s%s",db_path,DBPATH,dbsubpath[i]);
-		else safesnprintf(dbsubpath2,n2,"%s/%s",db_path,dbsubpath[i]);
+		else safesnprintf(dbsubpath2,n2,"%s%s",db_path,dbsubpath[i]);
 		
 		status_readdb_attrfix(dbsubpath2,i); // !TODO use sv_readdb ?
 		sv_readdb(dbsubpath1, "size_fix.txt",',',MAX_WEAPON_TYPE,MAX_WEAPON_TYPE,ARRAYLENGTH(atkmods),&status_readdb_sizefix, i);

+ 243 - 0
src/map/vending.c

@@ -2,6 +2,8 @@
 // For more information, see LICENCE in the main folder
 
 #include "../common/nullpo.h"
+#include "../common/malloc.h" // aMalloc, aFree
+#include "../common/showmsg.h" // ShowInfo
 #include "../common/strlib.h"
 #include "../common/utils.h"
 #include "clif.h"
@@ -18,10 +20,33 @@
 #include "log.h"
 
 #include <stdio.h>
+#include <stdlib.h> // atoi
 #include <string.h>
 
+struct vending_entry{
+	int cartinventory_id;
+	int amount;
+	int price;
+	int index;
+};
+
+struct vending{
+	int account_id;
+	int char_id;
+	int vendor_id;
+	int m;
+	int x;
+	int y;
+	unsigned char sex;
+	char title[MESSAGE_SIZE];
+	uint32 count;
+	struct vending_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 DBMap *autotrade_db;
 
 /**
  * Lookup to get the vending_db outside module
@@ -48,6 +73,11 @@ void vending_closevending(struct map_session_data* sd)
 	nullpo_retv(sd);
 
 	if( sd->state.vending ) {
+		if( Sql_Query( mmysql_handle, "DELETE FROM `%s` WHERE vending_id = %d;", vending_items_db, sd->vender_id ) != SQL_SUCCESS ||
+			Sql_Query( mmysql_handle, "DELETE FROM `%s` WHERE `id` = %d;", vendings_db, sd->vender_id ) != SQL_SUCCESS ){
+				Sql_ShowDebug(mmysql_handle);
+		}
+		
 		sd->state.vending = false;
 		clif_closevendingboard(&sd->bl, 0);
 		idb_remove(vending_db, sd->status.char_id);
@@ -198,6 +228,17 @@ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const ui
 		// vending item
 		pc_additem(sd, &vsd->status.cart[idx], amount, LOG_TYPE_VENDING);
 		vsd->vending[vend_list[i]].amount -= amount;
+
+		if( vsd->vending[vend_list[i]].amount ){
+			if( Sql_Query( mmysql_handle, "UPDATE `%s` SET `amount` = %d WHERE `vending_id` = %d and `cartinventory_id` = %d", vending_items_db, vsd->vending[vend_list[i]].amount, vsd->vender_id, vsd->status.cart[idx].id ) != SQL_SUCCESS ){
+				Sql_ShowDebug( mmysql_handle );
+			}
+		}else{
+			if( Sql_Query( mmysql_handle, "DELETE FROM `%s` WHERE `vending_id` = %d and `cartinventory_id` = %d", vending_items_db, vsd->vender_id, vsd->status.cart[idx].id ) != SQL_SUCCESS ){
+				Sql_ShowDebug( mmysql_handle );
+			}
+		}
+
 		pc_cart_delitem(vsd, idx, amount, 0, LOG_TYPE_VENDING);
 		clif_vendingreport(vsd, idx, amount);
 
@@ -253,12 +294,15 @@ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const ui
 void vending_openvending(struct map_session_data* sd, const char* message, const uint8* data, int count) {
 	int i, j;
 	int vending_skill_lvl;
+	char message_sql[MESSAGE_SIZE*2];
+	
 	nullpo_retv(sd);
 
 	if ( pc_isdead(sd) || !sd->state.prevend || pc_istrading(sd))
 		return; // can't open vendings lying dead || didn't use via the skill (wpe/hack) || can't have 2 shops at once
 
 	vending_skill_lvl = pc_checkskill(sd, MC_VENDING);
+	
 	// skill level and cart check
 	if( !vending_skill_lvl || !pc_iscarton(sd) ) {
 		clif_skill_fail(sd, MC_VENDING, USESKILL_FAIL_LEVEL, 0);
@@ -310,6 +354,18 @@ void vending_openvending(struct map_session_data* sd, const char* message, const
 	sd->vender_id = vending_getuid();
 	sd->vend_num = i;
 	safestrncpy(sd->message, message, MESSAGE_SIZE);
+	
+	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`) VALUES( %d, %d, %d, '%c', '%s', %d, %d, '%s', %d );", vendings_db, sd->vender_id, sd->status.account_id, sd->status.char_id, sd->status.sex == 2 ? 'F' : 'M', map[sd->bl.m].name, sd->bl.x, sd->bl.y, message_sql, sd->state.autotrade ) != 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);
+		}
+	}
 
 	clif_openvending(sd,sd->bl.id,sd->vending);
 	clif_showvendingboard(&sd->bl,message,0);
@@ -397,12 +453,198 @@ bool vending_searchall(struct map_session_data* sd, const struct s_search_store_
 	return true;
 }
 
+void vending_reopen( struct map_session_data* sd ){
+	int i, count;
+	uint8 *data, *p;
+	uint16 *index, *amount;
+	uint32 *value;
+	struct vending *vending;
+	struct vending_entry *entry;
+
+	vending = (struct vending*)idb_get( autotrade_db, sd->status.char_id );
+
+	if( !vending ){
+		map_quit(sd);
+		return;
+	}
+
+	if( vending->count <= 0 ){
+		idb_remove( autotrade_db, sd->status.char_id );
+		aFree(vending->entries);
+		aFree(vending);
+		map_quit(sd);
+		return;
+	}
+
+	data = (uint8*)aMalloc( vending->count * 8 );
+
+	for( i = 0, p = data, count = vending->count; i < vending->count; i++ ){
+		entry = &vending->entries[i];
+
+		index = (uint16*)(p + 0);
+		amount = (uint16*)(p + 2);
+		value = (uint32*)(p + 4);
+
+		ARR_FIND( 0, MAX_CART, entry->index, sd->status.cart[entry->index].id == entry->cartinventory_id );
+
+		if( entry->index == MAX_CART ){
+			count--;
+			continue;
+		}
+
+		*index = entry->index + 2;
+		*amount = entry->amount;
+		*value = entry->price;
+
+		p += 8;
+	}
+
+	if( !count ){
+		idb_remove( autotrade_db, sd->status.char_id );
+		aFree(vending->entries);
+		aFree(vending);
+		map_quit(sd);
+		return;
+	}
+
+	vending->count = count;
+
+	// Set him into a hacked prevend state
+	sd->state.prevend = 1;
+
+	// Open the shop again
+	vending_openvending( sd, vending->title, data, vending->count );
+
+	// Make him look perfect
+	unit_setdir(&sd->bl,battle_config.feature_autotrade_direction);
+
+	if( battle_config.feature_autotrade_sit ){
+		pc_setsit(sd);
+	}
+
+	idb_remove( autotrade_db, sd->status.char_id );
+
+	aFree(data);
+	aFree(vending->entries);
+	aFree(vending);
+}
+
+void do_init_vending_autotrade( void ){
+	if( battle_config.feature_autotrade ){
+		struct vending *autotraders;
+		struct vending *vending;
+		struct vending_entry *entry;
+		uint32 count;
+		int i, j;
+
+		if( Sql_Query(mmysql_handle, 
+			"SELECT `id`, `account_id`, `char_id`, `sex`, `map`, `x`, `y`, `title`"
+			"FROM `%s`"
+			"WHERE `autotrade` = 1;", vendings_db ) != SQL_SUCCESS ) {
+			Sql_ShowDebug(mmysql_handle);
+			return;
+		}
+
+		count = (uint32)Sql_NumRows(mmysql_handle);
+
+		if( count <= 0 ){
+			return;
+		}
+
+		autotraders = (struct vending*)aMalloc( sizeof( struct vending ) * count );
+		i = 0;
+
+		while( SQL_SUCCESS == Sql_NextRow(mmysql_handle) ) {
+			size_t len;
+			char* data;
+
+			vending = &autotraders[i];
+
+			Sql_GetData( mmysql_handle, 0, &data, NULL ); vending->vendor_id = atoi(data);
+			Sql_GetData( mmysql_handle, 1, &data, NULL ); vending->account_id = atoi(data);
+			Sql_GetData( mmysql_handle, 2, &data, NULL ); vending->char_id = atoi(data);
+			Sql_GetData( mmysql_handle, 3, &data, NULL ); vending->sex = data[0];
+			Sql_GetData( mmysql_handle, 4, &data, NULL ); vending->m = map_mapname2mapid( data );
+			Sql_GetData( mmysql_handle, 5, &data, NULL ); vending->x = atoi(data);
+			Sql_GetData( mmysql_handle, 6, &data, NULL ); vending->y = atoi(data);
+			Sql_GetData( mmysql_handle, 7, &data, &len ); safestrncpy( vending->title, data, min( len + 1, MESSAGE_SIZE ) );
+
+			vending->count = 0;
+
+			idb_put( autotrade_db, vending->char_id, vending );
+			
+			// initialize player
+			CREATE(vending->sd, TBL_PC, 1);
+
+			pc_setnewpc( vending->sd, vending->account_id, vending->char_id, 0, gettick(), vending->sex, 0 );
+
+			vending->sd->state.autotrade = 1;
+
+			chrif_authreq( vending->sd, true );
+
+			i++;
+		}
+
+		Sql_FreeResult( mmysql_handle );
+
+		for( i = 0; i < count; i++ ){
+			vending = &autotraders[i];
+
+			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, vending->vendor_id ) ) {
+				Sql_ShowDebug(mmysql_handle);
+				return;
+			}
+
+			vending->count = (uint32)Sql_NumRows(mmysql_handle);
+
+			if( vending->count <= 0 ){
+				// Player was not correctly deleted, must not be set online
+				idb_remove( autotrade_db, vending->char_id );
+				map_quit(vending->sd);
+				aFree(vending);
+				continue;
+			}
+		
+			vending->entries = (struct vending_entry*)aMalloc( sizeof( struct vending_entry ) * vending->count );
+			j = 0;
+
+			while( SQL_SUCCESS == Sql_NextRow(mmysql_handle) ) {
+				char* data;
+
+				entry = &vending->entries[j];
+			
+				Sql_GetData( mmysql_handle, 0, &data, NULL ); entry->cartinventory_id = atoi(data);
+				Sql_GetData( mmysql_handle, 1, &data, NULL ); entry->amount = atoi(data);
+				Sql_GetData( mmysql_handle, 2, &data, NULL ); entry->price = atoi(data);
+
+				j++;
+			}
+
+			Sql_FreeResult( mmysql_handle );
+		}
+
+		ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' autotraders.\n",count);
+	}
+	
+	// Everything is loaded fine, their entries will be reinserted once they are loaded
+	if( Sql_Query( mmysql_handle, "TRUNCATE TABLE `%s`;", vendings_db ) != SQL_SUCCESS ||
+		Sql_Query( mmysql_handle, "TRUNCATE TABLE `%s`;", vending_items_db ) != SQL_SUCCESS ){
+		Sql_ShowDebug(mmysql_handle);
+		return;
+	}
+}
+
 /**
  * Initialise the vending module
  * called in map::do_init
  */
 void do_final_vending(void) {
 	db_destroy(vending_db);
+	db_destroy(autotrade_db);
 }
 
 /**
@@ -411,5 +653,6 @@ void do_final_vending(void) {
  */
 void do_init_vending(void) {
 	vending_db = idb_alloc(DB_OPT_BASE);
+	autotrade_db = idb_alloc(DB_OPT_BASE);
 	vending_nextid = 0;
 }

+ 3 - 1
src/map/vending.h

@@ -18,7 +18,9 @@ struct s_vending {
 DBMap * vending_getdb();
 void do_final_vending(void);
 void do_init_vending(void);
-
+void do_init_vending_autotrade( void );
+ 
+void vending_reopen( struct map_session_data* sd );
 void vending_closevending(struct map_session_data* sd);
 void vending_openvending(struct map_session_data* sd, const char* message, const uint8* data, int count);
 void vending_vendinglistreq(struct map_session_data* sd, int id);