diff options
-rw-r--r-- | Changelog-Trunk.txt | 5 | ||||
-rw-r--r-- | conf/Changelog.txt | 2 | ||||
-rw-r--r-- | conf/char_athena.conf | 5 | ||||
-rw-r--r-- | sql-files/main.sql | 1 | ||||
-rw-r--r-- | sql-files/upgrade_svn14700.sql | 1 | ||||
-rw-r--r-- | src/char/char.c | 321 | ||||
-rw-r--r-- | src/char_sql/char.c | 266 | ||||
-rw-r--r-- | src/common/mmo.h | 3 | ||||
-rw-r--r-- | src/login/account_sql.c | 2 | ||||
-rw-r--r-- | src/login/login.c | 7 |
10 files changed, 588 insertions, 25 deletions
diff --git a/Changelog-Trunk.txt b/Changelog-Trunk.txt index adbb1d5af..b859a239b 100644 --- a/Changelog-Trunk.txt +++ b/Changelog-Trunk.txt @@ -1,6 +1,11 @@ Date Added 2011/02/07 + * Added support for new delayed character deletion. [Ai4rei] + - Asks for birth date associated with the account and has a waiting time of 24 hours by default (setting). + - For SQL apply upgrade_svn14700.sql to upgrade table `char`; for TXT no action is necessary, as it upgrades itself. + - This completes support for clients 2010-08-03aRagexeRE and later. + * Updated login sql engine version, missed during `birthdate` addition (follow up to r14672). * Updates to various client packets. [Ai4rei] - Renamed clif_set0199 and clif_send0199 to clif_map_property and clif_map_property_mapall respectively and added an enumeration for currently known map properties. - Renamed clif_set01D6 to clif_map_type and added an enumeration for currently known map types. diff --git a/conf/Changelog.txt b/conf/Changelog.txt index 8061c8929..219621ce6 100644 --- a/conf/Changelog.txt +++ b/conf/Changelog.txt @@ -1,5 +1,7 @@ Date Added +2011/02/06 + * Rev. 14700 Added char-server setting 'char_del_delay'. [Ai4rei] 2011/01/13 * Rev. 14667 Removed ladmin settings (ladmin_athena.conf) and login-server settings for ladmin (login_athena.conf) (topic:262934). [Ai4rei] 2010/12/30 diff --git a/conf/char_athena.conf b/conf/char_athena.conf index af0da9976..8e109f4da 100644 --- a/conf/char_athena.conf +++ b/conf/char_athena.conf @@ -166,6 +166,11 @@ chars_per_account: 0 // e.g. char_del_level: 80 (players can't delete characters with 80+ BaseLevel) char_del_level: 0 +// Amount of time in seconds by which the character deletion is delayed. +// Default: 86400 (24 hours) +// NOTE: Requires client 2010-08-03aragexeRE or newer. +char_del_delay: 86400 + // What folder the DB files are in (item_db.txt, etc.) db_path: db diff --git a/sql-files/main.sql b/sql-files/main.sql index 55cc8b4bc..dae3300e4 100644 --- a/sql-files/main.sql +++ b/sql-files/main.sql @@ -101,6 +101,7 @@ CREATE TABLE IF NOT EXISTS `char` ( `child` int(11) unsigned NOT NULL default '0', `fame` int(11) unsigned NOT NULL default '0', `rename` SMALLINT(3) unsigned NOT NULL default '0', + `delete_date` INT(11) UNSIGNED NOT NULL DEFAULT '0', PRIMARY KEY (`char_id`), KEY `account_id` (`account_id`), KEY `party_id` (`party_id`), diff --git a/sql-files/upgrade_svn14700.sql b/sql-files/upgrade_svn14700.sql new file mode 100644 index 000000000..a1e3d8ddb --- /dev/null +++ b/sql-files/upgrade_svn14700.sql @@ -0,0 +1 @@ +ALTER TABLE `char` ADD `delete_date` INT(11) UNSIGNED NOT NULL DEFAULT '0'; diff --git a/src/char/char.c b/src/char/char.c index 6523decf7..a4bd3d6ed 100644 --- a/src/char/char.c +++ b/src/char/char.c @@ -85,6 +85,7 @@ char char_name_letters[1024] = ""; // list of letters/symbols allowed (or not) i int char_per_account = 0; //Maximum charas per account (default unlimited) [Sirius] int char_del_level = 0; //From which level u can delete character [Lupus] +int char_del_delay = 86400; int log_char = 1; // loggin char or not [devil] int log_inter = 1; // loggin inter or not [devil] @@ -106,6 +107,7 @@ struct char_session_data { int gmlevel; uint32 version; uint8 clienttype; + char birthdate[10+1]; // YYYY-MM-DD }; int char_id_count = START_CHAR_NUM; @@ -368,6 +370,48 @@ int char_log(char *fmt, ...) return 0; } + +/// Find all characters for given session and update the session character cache. +int char_find_characters(struct char_session_data* sd) +{ + int i, found_num = 0; + + for( i = 0; i < char_num; i++ ) + {// find character entries and save them + if( char_dat[i].status.account_id == sd->account_id ) + { + sd->found_char[found_num++] = i; + + if( found_num >= MAX_CHARS ) + { + break; + } + } + } + + for( i = found_num; i < MAX_CHARS; i++ ) + {// fill remaining blanks + sd->found_char[i] = -1; + } + + return found_num; +} + + +/// Search character data from given session. +struct mmo_charstatus* search_session_character(struct char_session_data* sd, int char_id) +{ + int i; + + ARR_FIND( 0, MAX_CHARS, i, sd->found_char[i] != -1 && char_dat[sd->found_char[i]].status.char_id == char_id ); + if( i == MAX_CHARS ) + { + return NULL; + } + return &char_dat[sd->found_char[i]].status; +} + + //Search character data from the aid/cid givem struct mmo_charstatus* search_character(int aid, int cid) { @@ -483,7 +527,7 @@ int mmo_char_tostr(char *str, struct mmo_charstatus *p, struct global_reg *reg, "\t%d,%d,%d\t%d,%d,%d,%d" //Up to hom id "\t%d,%d,%d\t%d,%d,%d,%d,%d" //Up to head bottom "\t%d,%d,%d\t%d,%d,%d" //last point + save point - ",%d,%d,%d,%d,%d\t", //Family info + ",%d,%d,%d,%d,%d,%lu\t", //Family info + delete date p->char_id, p->account_id, p->slot, p->name, // p->class_, p->base_level, p->job_level, p->base_exp, p->job_exp, p->zeny, @@ -496,7 +540,8 @@ int mmo_char_tostr(char *str, struct mmo_charstatus *p, struct global_reg *reg, p->weapon, p->shield, p->head_top, p->head_mid, p->head_bottom, p->last_point.map, p->last_point.x, p->last_point.y, // p->save_point.map, p->save_point.x, p->save_point.y, - p->partner_id,p->father,p->mother,p->child,p->fame); + p->partner_id,p->father,p->mother,p->child,p->fame, // + TOL(p->delete_date)); // FIXME: platform-dependent size for(i = 0; i < MAX_MEMOPOINTS; i++) if (p->memo_point[i].map) { str_p += sprintf(str_p, "%d,%d,%d ", p->memo_point[i].map, p->memo_point[i].x, p->memo_point[i].y); @@ -549,10 +594,30 @@ int mmo_char_fromstr(char *str, struct mmo_charstatus *p, struct global_reg *reg int tmp_int[256]; unsigned int tmp_uint[2]; //To read exp.... int next, len, i, j; + unsigned long tmp_ulong[1]; // initilialise character memset(p, '\0', sizeof(struct mmo_charstatus)); +// Char structure of version 146xx (delete date) + if (sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%u,%u,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%d,%d,%d\t%d,%d,%d,%d,%d,%d,%d,%d,%lu%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0], + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_uint[0], &tmp_uint[1], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], &tmp_int[26], &tmp_int[44], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + &tmp_int[45], &tmp_int[35], &tmp_int[36], + &tmp_int[46], &tmp_int[37], &tmp_int[38], &tmp_int[39], + &tmp_int[40], &tmp_int[41], &tmp_int[42], &tmp_int[43], &tmp_ulong[0], &next) != 49) + { + tmp_ulong[0] = 0; // delete date // Char structure of version 1500 (homun + mapindex maps) if (sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%u,%u,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" "\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" @@ -677,6 +742,7 @@ int mmo_char_fromstr(char *str, struct mmo_charstatus *p, struct global_reg *reg tmp_int[45] = mapindex_name2id(tmp_str[1]); tmp_int[46] = mapindex_name2id(tmp_str[2]); } // Char structure of version 1500 (homun + mapindex maps) + } // Char structure of version 146xx (delete date) safestrncpy(p->name, tmp_str[0], NAME_LENGTH); //Overflow protection [Skotlex] p->char_id = tmp_int[0]; @@ -726,6 +792,7 @@ int mmo_char_fromstr(char *str, struct mmo_charstatus *p, struct global_reg *reg p->hom_id = tmp_int[44]; p->last_point.map = tmp_int[45]; p->save_point.map = tmp_int[46]; + p->delete_date = tmp_ulong[0]; #ifndef TXT_SQL_CONVERT // Some checks @@ -1705,7 +1772,7 @@ int count_users(void) // Writes char data to the buffer in the format used by the client. // Used in packets 0x6b (chars info) and 0x6d (new char info) // Returns the size -#define MAX_CHAR_BUF 110 //Max size (for WFIFOHEAD calls) +#define MAX_CHAR_BUF 132 //Max size (for WFIFOHEAD calls) int mmo_char_tobuf(uint8* buffer, struct mmo_charstatus* p) { unsigned short offset = 0; @@ -1765,6 +1832,10 @@ int mmo_char_tobuf(uint8* buffer, struct mmo_charstatus* p) mapindex_getmapname_ext(mapindex_id2name(p->last_point.map), (char*)WBUFP(buf,108)); offset += MAP_NAME_LENGTH_EXT; #endif +#if PACKETVER >= 20100803 + WBUFL(buf,124) = TOL(p->delete_date); + offset += 4; +#endif return 106+offset; } @@ -1778,16 +1849,7 @@ int mmo_char_send006b(int fd, struct char_session_data* sd) offset += 3; #endif - found_num = 0; - for(i = 0; i < char_num; i++) { - if (char_dat[i].status.account_id == sd->account_id) { - sd->found_char[found_num] = i; - if( ++found_num == MAX_CHARS ) - break; - } - } - for(i = found_num; i < MAX_CHARS; i++) - sd->found_char[i] = -1; + found_num = char_find_characters(sd); j = 24 + offset; // offset WFIFOHEAD(fd,j + found_num*MAX_CHAR_BUF); @@ -2064,7 +2126,7 @@ int parse_fromlogin(int fd) break; case 0x2717: // account data - if (RFIFOREST(fd) < 51) + if (RFIFOREST(fd) < 62) return 0; // find the authenticated session with this account id @@ -2074,6 +2136,7 @@ int parse_fromlogin(int fd) memcpy(sd->email, RFIFOP(fd,6), 40); sd->expiration_time = (time_t)RFIFOL(fd,46); sd->gmlevel = RFIFOB(fd,50); + safestrncpy(sd->birthdate, RFIFOP(fd,51), sizeof(sd->birthdate)); // continued from char_auth_ok... if( max_connect_user && count_users() >= max_connect_user && sd->gmlevel < gm_allow_level ) @@ -2089,7 +2152,7 @@ int parse_fromlogin(int fd) mmo_char_send006b(i, sd); } } - RFIFOSKIP(fd,51); + RFIFOSKIP(fd,62); break; // login-server alive packet @@ -3155,6 +3218,211 @@ int lan_subnetcheck(uint32 ip) } } + +/// @param result +/// 0 (0x718): An unknown error has occurred. +/// 1: none/success +/// 3 (0x719): A database error occurred. +/// 4 (0x71a): To delete a character you must withdraw from the guild. +/// 5 (0x71b): To delete a character you must withdraw from the party. +/// Any (0x718): An unknown error has occurred. +void char_delete2_ack(int fd, int char_id, uint32 result, time_t delete_date) +{// HC: <0828>.W <char id>.L <Msg:0-5>.L <deleteDate>.L + WFIFOHEAD(fd,14); + WFIFOW(fd,0) = 0x828; + WFIFOL(fd,2) = char_id; + WFIFOL(fd,6) = result; + WFIFOL(fd,10) = TOL(delete_date); + WFIFOSET(fd,14); +} + + +/// @param result +/// 0 (0x718): An unknown error has occurred. +/// 1: none/success +/// 2 (0x71c): Due to system settings can not be deleted. +/// 3 (0x719): A database error occurred. +/// 4 (0x71d): Deleting not yet possible time. +/// 5 (0x71e): Date of birth do not match. +/// Any (0x718): An unknown error has occurred. +void char_delete2_accept_ack(int fd, int char_id, uint32 result) +{// HC: <082a>.W <char id>.L <Msg:0-5>.L + WFIFOHEAD(fd,10); + WFIFOW(fd,0) = 0x82a; + WFIFOL(fd,2) = char_id; + WFIFOL(fd,6) = result; + WFIFOSET(fd,10); +} + + +/// @param result +/// 1 (0x718): none/success, (if char id not in deletion process): An unknown error has occurred. +/// 2 (0x719): A database error occurred. +/// Any (0x718): An unknown error has occurred. +void char_delete2_cancel_ack(int fd, int char_id, uint32 result) +{// HC: <082c>.W <char id>.L <Msg:1-2>.L + WFIFOHEAD(fd,10); + WFIFOW(fd,0) = 0x82c; + WFIFOL(fd,2) = char_id; + WFIFOL(fd,6) = result; + WFIFOSET(fd,10); +} + + +static void char_delete2_req(int fd, struct char_session_data* sd) +{// CH: <0827>.W <char id>.L + int char_id; + struct mmo_charstatus* cs; + + char_id = RFIFOL(fd,2); + + if( ( cs = search_session_character(sd, char_id) ) == NULL ) + {// character not found + char_delete2_ack(fd, char_id, 3, 0); + return; + } + + if( cs->delete_date ) + {// character already queued for deletion + char_delete2_ack(fd, char_id, 0, 0); + return; + } + +/* + // Aegis imposes these checks probably to avoid dead member + // entries in guilds/parties, otherwise they are not required. + // TODO: Figure out how these are enforced during waiting. + if( cs->guild_id ) + {// character in guild + char_delete2_ack(fd, char_id, 4, 0); + return; + } + + if( cs->party_id ) + {// character in party + char_delete2_ack(fd, char_id, 5, 0); + return; + } +*/ + + // success + cs->delete_date = time(NULL)+char_del_delay; + + char_delete2_ack(fd, char_id, 1, cs->delete_date); +} + + +static void char_delete2_accept(int fd, struct char_session_data* sd) +{// CH: <0829>.W <char id>.L <birth date:YYMMDD>.6B + char birthdate[8+1]; + int char_id, i; + struct mmo_charstatus* cs; + + char_id = RFIFOL(fd,2); + + ShowInfo(CL_RED"Request Char Deletion: "CL_GREEN"%d (%d)"CL_RESET"\n", sd->account_id, char_id); + + // construct "YY-MM-DD" + birthdate[0] = RFIFOB(fd,6); + birthdate[1] = RFIFOB(fd,7); + birthdate[2] = '-'; + birthdate[3] = RFIFOB(fd,8); + birthdate[4] = RFIFOB(fd,9); + birthdate[5] = '-'; + birthdate[6] = RFIFOB(fd,10); + birthdate[7] = RFIFOB(fd,11); + birthdate[8] = 0; + + ARR_FIND( 0, MAX_CHARS, i, sd->found_char[i] != -1 && char_dat[sd->found_char[i]].status.char_id == char_id ); + if( i == MAX_CHARS ) + {// character not found + char_delete2_accept_ack(fd, char_id, 3); + return; + } + cs = &char_dat[sd->found_char[i]].status; + + if( !cs->delete_date || cs->delete_date>time(NULL) ) + {// not queued or delay not yet passed + char_delete2_accept_ack(fd, char_id, 4); + return; + } + + if( strcmp(sd->birthdate+2, birthdate) ) // +2 to cut off the century + {// birth date is wrong + char_delete2_accept_ack(fd, char_id, 5); + return; + } + + if( ( char_del_level > 0 && cs->base_level >= (unsigned int)char_del_level ) || ( char_del_level < 0 && cs->base_level <= (unsigned int)(-char_del_level) ) ) + {// character level config restriction + char_delete2_accept_ack(fd, char_id, 2); + return; + } + + // success + char_delete(cs); + + // drop character entry + if( --char_num > 0 && sd->found_char[i] != char_num ) + { + int s, c; + + // move the last entry to the place of the deleted character + memcpy(&char_dat[sd->found_char[i]], &char_dat[char_num], sizeof(struct mmo_charstatus)); + + // scan currently online accounts, if the moved character + // entry requires an update of the cached character list + for( s = 0; s < fd_max; s++ ) + { + struct char_session_data* osd; + + if( session[s] && ( osd = (struct char_session_data*)session[s]->session_data ) != NULL && osd->account_id == char_dat[char_num].status.account_id ) + { + for( c = 0; c < MAX_CHARS; c++ ) + { + if( osd->found_char[c] == char_num ) + { + osd->found_char[c] = sd->found_char[i]; + break; + } + } + break; + } + } + + // wipe the last entry + memset(&char_dat[char_num], 0, sizeof(struct mmo_charstatus)); + } + + // refresh character list cache + char_find_characters(sd); + + char_delete2_accept_ack(fd, char_id, 1); +} + + +static void char_delete2_cancel(int fd, struct char_session_data* sd) +{// CH: <082b>.W <char id>.L + int char_id; + struct mmo_charstatus* cs; + + char_id = RFIFOL(fd,2); + + if( ( cs = search_session_character(sd, char_id) ) == NULL ) + {// character not found + char_delete2_cancel_ack(fd, char_id, 2); + return; + } + + // there is no need to check, whether or not the character was + // queued for deletion, as the client prints an error message by + // itself, if it was not the case (@see char_delete2_cancel_ack) + cs->delete_date = 0; + + char_delete2_cancel_ack(fd, char_id, 1); +} + + int parse_char(int fd) { int i, ch; @@ -3600,6 +3868,27 @@ int parse_char(int fd) RFIFOSKIP(fd,32); break; + // deletion timer request + case 0x827: + FIFOSD_CHECK(6); + char_delete2_req(fd, sd); + RFIFOSKIP(fd,6); + break; + + // deletion accept request + case 0x829: + FIFOSD_CHECK(12); + char_delete2_accept(fd, sd); + RFIFOSKIP(fd,12); + break; + + // deletion cancel request + case 0x82b: + FIFOSD_CHECK(6); + char_delete2_cancel(fd, sd); + RFIFOSKIP(fd,6); + break; + // login as map-server case 0x2af8: if (RFIFOREST(fd) < 60) @@ -4056,6 +4345,8 @@ int char_config_read(const char *cfgName) char_per_account = atoi(w2); } else if (strcmpi(w1, "char_del_level") == 0) { //disable/enable char deletion by its level condition [Lupus] char_del_level = atoi(w2); + } else if (strcmpi(w1, "char_del_delay") == 0) { + char_del_delay = atoi(w2); // online files options } else if (strcmpi(w1, "online_txt_filename") == 0) { safestrncpy(online_txt_filename, w2, sizeof(online_txt_filename)); diff --git a/src/char_sql/char.c b/src/char_sql/char.c index 262f585d2..ee73606ef 100644 --- a/src/char_sql/char.c +++ b/src/char_sql/char.c @@ -109,6 +109,7 @@ char char_name_letters[1024] = ""; // list of letters/symbols allowed (or not) i int char_per_account = 0; //Maximum charas per account (default unlimited) [Sirius] int char_del_level = 0; //From which level u can delete character [Lupus] +int char_del_delay = 86400; int log_char = 1; // loggin char or not [devil] int log_inter = 1; // loggin inter or not [devil] @@ -131,6 +132,7 @@ struct char_session_data { uint32 version; uint8 clienttype; char new_name[NAME_LENGTH]; + char birthdate[10+1]; // YYYY-MM-DD }; int max_connect_user = 0; @@ -471,7 +473,7 @@ int mmo_char_tosql(int char_id, struct mmo_charstatus* p) (p->party_id != cp->party_id) || (p->guild_id != cp->guild_id) || (p->pet_id != cp->pet_id) || (p->weapon != cp->weapon) || (p->hom_id != cp->hom_id) || (p->shield != cp->shield) || (p->head_top != cp->head_top) || - (p->head_mid != cp->head_mid) || (p->head_bottom != cp->head_bottom) + (p->head_mid != cp->head_mid) || (p->head_bottom != cp->head_bottom) || (p->delete_date != cp->delete_date) ) { //Save status if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `base_level`='%d', `job_level`='%d'," @@ -480,7 +482,8 @@ int mmo_char_tosql(int char_id, struct mmo_charstatus* p) "`str`='%d',`agi`='%d',`vit`='%d',`int`='%d',`dex`='%d',`luk`='%d'," "`option`='%d',`party_id`='%d',`guild_id`='%d',`pet_id`='%d',`homun_id`='%d'," "`weapon`='%d',`shield`='%d',`head_top`='%d',`head_mid`='%d',`head_bottom`='%d'," - "`last_map`='%s',`last_x`='%d',`last_y`='%d',`save_map`='%s',`save_x`='%d',`save_y`='%d', `rename`='%d'" + "`last_map`='%s',`last_x`='%d',`last_y`='%d',`save_map`='%s',`save_x`='%d',`save_y`='%d', `rename`='%d'," + "`delete_date`='%lu'" " WHERE `account_id`='%d' AND `char_id` = '%d'", char_db, p->base_level, p->job_level, p->base_exp, p->job_exp, p->zeny, @@ -490,6 +493,7 @@ int mmo_char_tosql(int char_id, struct mmo_charstatus* p) p->weapon, p->shield, p->head_top, p->head_mid, p->head_bottom, mapindex_id2name(p->last_point.map), p->last_point.x, p->last_point.y, mapindex_id2name(p->save_point.map), p->save_point.x, p->save_point.y, p->rename, + TOL(p->delete_date), // FIXME: platform-dependent size p->account_id, p->char_id) ) { Sql_ShowDebug(sql_handle); @@ -839,7 +843,7 @@ int mmo_chars_fromsql(struct char_session_data* sd, uint8* buf) "`char_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`," "`str`,`agi`,`vit`,`int`,`dex`,`luk`,`max_hp`,`hp`,`max_sp`,`sp`," "`status_point`,`skill_point`,`option`,`karma`,`manner`,`hair`,`hair_color`," - "`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`,`last_map`,`rename`" + "`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`,`last_map`,`rename`,`delete_date`" " FROM `%s` WHERE `account_id`='%d' AND `char_num` < '%d'", char_db, sd->account_id, MAX_CHARS) || SQL_ERROR == SqlStmt_Execute(stmt) || SQL_ERROR == SqlStmt_BindColumn(stmt, 0, SQLDT_INT, &p.char_id, 0, NULL, NULL) @@ -876,6 +880,7 @@ int mmo_chars_fromsql(struct char_session_data* sd, uint8* buf) || SQL_ERROR == SqlStmt_BindColumn(stmt, 31, SQLDT_SHORT, &p.head_bottom, 0, NULL, NULL) || SQL_ERROR == SqlStmt_BindColumn(stmt, 32, SQLDT_STRING, &last_map, sizeof(last_map), NULL, NULL) || SQL_ERROR == SqlStmt_BindColumn(stmt, 33, SQLDT_SHORT, &p.rename, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 34, SQLDT_UINT32, &p.delete_date, 0, NULL, NULL) ) { SqlStmt_ShowDebug(stmt); @@ -934,7 +939,7 @@ int mmo_char_fromsql(int char_id, struct mmo_charstatus* p, bool load_everything "`str`,`agi`,`vit`,`int`,`dex`,`luk`,`max_hp`,`hp`,`max_sp`,`sp`," "`status_point`,`skill_point`,`option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`,`homun_id`,`hair`," "`hair_color`,`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`,`last_map`,`last_x`,`last_y`," - "`save_map`,`save_x`,`save_y`,`partner_id`,`father`,`mother`,`child`,`fame`,`rename`" + "`save_map`,`save_x`,`save_y`,`partner_id`,`father`,`mother`,`child`,`fame`,`rename`,`delete_date`" " FROM `%s` WHERE `char_id`=? LIMIT 1", char_db) || SQL_ERROR == SqlStmt_BindParam(stmt, 0, SQLDT_INT, &char_id, 0) || SQL_ERROR == SqlStmt_Execute(stmt) @@ -987,6 +992,7 @@ int mmo_char_fromsql(int char_id, struct mmo_charstatus* p, bool load_everything || SQL_ERROR == SqlStmt_BindColumn(stmt, 46, SQLDT_INT, &p->child, 0, NULL, NULL) || SQL_ERROR == SqlStmt_BindColumn(stmt, 47, SQLDT_INT, &p->fame, 0, NULL, NULL) || SQL_ERROR == SqlStmt_BindColumn(stmt, 48, SQLDT_SHORT, &p->rename, 0, NULL, NULL) + || SQL_ERROR == SqlStmt_BindColumn(stmt, 49, SQLDT_UINT32, &p->delete_date, 0, NULL, NULL) ) { SqlStmt_ShowDebug(stmt); @@ -1407,6 +1413,7 @@ int delete_char_sql(int char_id) Sql_FreeResult(sql_handle); //check for config char del condition [Lupus] + // TODO: Move this out to packet processing (0x68/0x1fb). if( ( char_del_level > 0 && base_level >= char_del_level ) || ( char_del_level < 0 && base_level <= -char_del_level ) ) { @@ -1542,7 +1549,7 @@ int count_users(void) // Writes char data to the buffer in the format used by the client. // Used in packets 0x6b (chars info) and 0x6d (new char info) // Returns the size -#define MAX_CHAR_BUF 110 //Max size (for WFIFOHEAD calls) +#define MAX_CHAR_BUF 132 //Max size (for WFIFOHEAD calls) int mmo_char_tobuf(uint8* buffer, struct mmo_charstatus* p) { unsigned short offset = 0; @@ -1602,6 +1609,10 @@ int mmo_char_tobuf(uint8* buffer, struct mmo_charstatus* p) mapindex_getmapname_ext(mapindex_id2name(p->last_point.map), (char*)WBUFP(buf,108)); offset += MAP_NAME_LENGTH_EXT; #endif +#if PACKETVER >= 20100803 + WBUFL(buf,124) = TOL(p->delete_date); + offset += 4; +#endif return 106+offset; } @@ -1849,7 +1860,7 @@ int parse_fromlogin(int fd) break; case 0x2717: // account data - if (RFIFOREST(fd) < 51) + if (RFIFOREST(fd) < 62) return 0; // find the authenticated session with this account id @@ -1859,6 +1870,7 @@ int parse_fromlogin(int fd) memcpy(sd->email, RFIFOP(fd,6), 40); sd->expiration_time = (time_t)RFIFOL(fd,46); sd->gmlevel = RFIFOB(fd,50); + safestrncpy(sd->birthdate, RFIFOP(fd,51), sizeof(sd->birthdate)); // continued from char_auth_ok... if( max_connect_user && count_users() >= max_connect_user && sd->gmlevel < gm_allow_level ) @@ -1874,7 +1886,7 @@ int parse_fromlogin(int fd) mmo_char_send006b(i, sd); } } - RFIFOSKIP(fd,51); + RFIFOSKIP(fd,62); break; // login-server alive packet @@ -2961,6 +2973,223 @@ int lan_subnetcheck(uint32 ip) } } + +/// @param result +/// 0 (0x718): An unknown error has occurred. +/// 1: none/success +/// 3 (0x719): A database error occurred. +/// 4 (0x71a): To delete a character you must withdraw from the guild. +/// 5 (0x71b): To delete a character you must withdraw from the party. +/// Any (0x718): An unknown error has occurred. +void char_delete2_ack(int fd, int char_id, uint32 result, time_t delete_date) +{// HC: <0828>.W <char id>.L <Msg:0-5>.L <deleteDate>.L + WFIFOHEAD(fd,14); + WFIFOW(fd,0) = 0x828; + WFIFOL(fd,2) = char_id; + WFIFOL(fd,6) = result; + WFIFOL(fd,10) = TOL(delete_date); + WFIFOSET(fd,14); +} + + +/// @param result +/// 0 (0x718): An unknown error has occurred. +/// 1: none/success +/// 2 (0x71c): Due to system settings can not be deleted. +/// 3 (0x719): A database error occurred. +/// 4 (0x71d): Deleting not yet possible time. +/// 5 (0x71e): Date of birth do not match. +/// Any (0x718): An unknown error has occurred. +void char_delete2_accept_ack(int fd, int char_id, uint32 result) +{// HC: <082a>.W <char id>.L <Msg:0-5>.L + WFIFOHEAD(fd,10); + WFIFOW(fd,0) = 0x82a; + WFIFOL(fd,2) = char_id; + WFIFOL(fd,6) = result; + WFIFOSET(fd,10); +} + + +/// @param result +/// 1 (0x718): none/success, (if char id not in deletion process): An unknown error has occurred. +/// 2 (0x719): A database error occurred. +/// Any (0x718): An unknown error has occurred. +void char_delete2_cancel_ack(int fd, int char_id, uint32 result) +{// HC: <082c>.W <char id>.L <Msg:1-2>.L + WFIFOHEAD(fd,10); + WFIFOW(fd,0) = 0x82c; + WFIFOL(fd,2) = char_id; + WFIFOL(fd,6) = result; + WFIFOSET(fd,10); +} + + +static void char_delete2_req(int fd, struct char_session_data* sd) +{// CH: <0827>.W <char id>.L + int char_id, i, guild_id, party_id; + char* data; + time_t delete_date; + + char_id = RFIFOL(fd,2); + + ARR_FIND( 0, MAX_CHARS, i, sd->found_char[i] == char_id ); + if( i == MAX_CHARS ) + {// character not found + char_delete2_ack(fd, char_id, 3, 0); + return; + } + + if( SQL_SUCCESS != Sql_Query(sql_handle, "SELECT `guild_id`,`party_id`,`delete_date` FROM `%s` WHERE `char_id`='%d'", char_db, char_id) || SQL_SUCCESS != Sql_NextRow(sql_handle) ) + { + Sql_ShowDebug(sql_handle); + char_delete2_ack(fd, char_id, 3, 0); + return; + } + + Sql_GetData(sql_handle, 0, &data, NULL); guild_id = atoi(data); + Sql_GetData(sql_handle, 1, &data, NULL); party_id = atoi(data); + Sql_GetData(sql_handle, 2, &data, NULL); delete_date = strtoul(data, NULL, 10); + + if( delete_date ) + {// character already queued for deletion + char_delete2_ack(fd, char_id, 0, 0); + return; + } + +/* + // Aegis imposes these checks probably to avoid dead member + // entries in guilds/parties, otherwise they are not required. + // TODO: Figure out how these are enforced during waiting. + if( guild_id ) + {// character in guild + char_delete2_ack(fd, char_id, 4, 0); + return; + } + + if( party_id ) + {// character in party + char_delete2_ack(fd, char_id, 5, 0); + return; + } +*/ + + // success + delete_date = time(NULL)+char_del_delay; + + if( SQL_SUCCESS != Sql_Query(sql_handle, "UPDATE `%s` SET `delete_date`='%lu' WHERE `char_id`='%d'", char_db, TOL(delete_date), char_id) ) + { + Sql_ShowDebug(sql_handle); + char_delete2_ack(fd, char_id, 3, 0); + return; + } + + char_delete2_ack(fd, char_id, 1, delete_date); +} + + +static void char_delete2_accept(int fd, struct char_session_data* sd) +{// CH: <0829>.W <char id>.L <birth date:YYMMDD>.6B + char birthdate[8+1]; + int char_id, i, k; + unsigned int base_level; + char* data; + time_t delete_date; + + char_id = RFIFOL(fd,2); + + ShowInfo(CL_RED"Request Char Deletion: "CL_GREEN"%d (%d)"CL_RESET"\n", sd->account_id, char_id); + + // construct "YY-MM-DD" + birthdate[0] = RFIFOB(fd,6); + birthdate[1] = RFIFOB(fd,7); + birthdate[2] = '-'; + birthdate[3] = RFIFOB(fd,8); + birthdate[4] = RFIFOB(fd,9); + birthdate[5] = '-'; + birthdate[6] = RFIFOB(fd,10); + birthdate[7] = RFIFOB(fd,11); + birthdate[8] = 0; + + ARR_FIND( 0, MAX_CHARS, i, sd->found_char[i] == char_id ); + if( i == MAX_CHARS ) + {// character not found + char_delete2_accept_ack(fd, char_id, 3); + return; + } + + if( SQL_SUCCESS != Sql_Query(sql_handle, "SELECT `base_level`,`delete_date` FROM `%s` WHERE `char_id`='%d'", char_db, char_id) || SQL_SUCCESS != Sql_NextRow(sql_handle) ) + {// data error + Sql_ShowDebug(sql_handle); + char_delete2_accept_ack(fd, char_id, 3); + return; + } + + Sql_GetData(sql_handle, 0, &data, NULL); base_level = (unsigned int)strtoul(data, NULL, 10); + Sql_GetData(sql_handle, 1, &data, NULL); delete_date = strtoul(data, NULL, 10); + + if( !delete_date || delete_date>time(NULL) ) + {// not queued or delay not yet passed + char_delete2_accept_ack(fd, char_id, 4); + return; + } + + if( strcmp(sd->birthdate+2, birthdate) ) // +2 to cut off the century + {// birth date is wrong + char_delete2_accept_ack(fd, char_id, 5); + return; + } + + if( ( char_del_level > 0 && base_level >= (unsigned int)char_del_level ) || ( char_del_level < 0 && base_level <= (unsigned int)(-char_del_level) ) ) + {// character level config restriction + char_delete2_accept_ack(fd, char_id, 2); + return; + } + + // success + if( delete_char_sql(char_id) < 0 ) + { + char_delete2_accept_ack(fd, char_id, 3); + return; + } + + // refresh character list cache + for(k = i; k < MAX_CHARS-1; k++) + { + sd->found_char[k] = sd->found_char[k+1]; + } + sd->found_char[MAX_CHARS-1] = -1; + + char_delete2_accept_ack(fd, char_id, 1); +} + + +static void char_delete2_cancel(int fd, struct char_session_data* sd) +{// CH: <082b>.W <char id>.L + int char_id, i; + + char_id = RFIFOL(fd,2); + + ARR_FIND( 0, MAX_CHARS, i, sd->found_char[i] == char_id ); + if( i == MAX_CHARS ) + {// character not found + char_delete2_cancel_ack(fd, char_id, 2); + return; + } + + // there is no need to check, whether or not the character was + // queued for deletion, as the client prints an error message by + // itself, if it was not the case (@see char_delete2_cancel_ack) + if( SQL_SUCCESS != Sql_Query(sql_handle, "UPDATE `%s` SET `delete_date`='0' WHERE `char_id`='%d'", char_db, char_id) ) + { + Sql_ShowDebug(sql_handle); + char_delete2_cancel_ack(fd, char_id, 2); + return; + } + + char_delete2_cancel_ack(fd, char_id, 1); +} + + int parse_char(int fd) { int i, ch; @@ -3393,6 +3622,27 @@ int parse_char(int fd) RFIFOSKIP(fd,32); break; + // deletion timer request + case 0x827: + FIFOSD_CHECK(6); + char_delete2_req(fd, sd); + RFIFOSKIP(fd,6); + break; + + // deletion accept request + case 0x829: + FIFOSD_CHECK(12); + char_delete2_accept(fd, sd); + RFIFOSKIP(fd,12); + break; + + // deletion cancel request + case 0x82b: + FIFOSD_CHECK(6); + char_delete2_cancel(fd, sd); + RFIFOSKIP(fd,6); + break; + // login as map-server case 0x2af8: if (RFIFOREST(fd) < 60) @@ -3910,6 +4160,8 @@ int char_config_read(const char* cfgName) char_per_account = atoi(w2); } else if (strcmpi(w1, "char_del_level") == 0) { //disable/enable char deletion by its level condition [Lupus] char_del_level = atoi(w2); + } else if (strcmpi(w1, "char_del_delay") == 0) { + char_del_delay = atoi(w2); } else if(strcmpi(w1,"db_path")==0) { safestrncpy(db_path, w2, sizeof(db_path)); } else if (strcmpi(w1, "console") == 0) { diff --git a/src/common/mmo.h b/src/common/mmo.h index d39fe16e0..181f972e5 100644 --- a/src/common/mmo.h +++ b/src/common/mmo.h @@ -38,6 +38,7 @@ // 20100629 - 2010-06-29aRagexeRE+ - 0x2d0, 0xaa, 0x2d1, 0x2d2 // 20100721 - 2010-07-21aRagexeRE+ - 0x6b, 0x6d // 20100727 - 2010-07-27aRagexeRE+ - 0x6b, 0x6d +// 20100803 - 2010-08-03aRagexeRE+ - 0x6b, 0x6d, 0x827, 0x828, 0x829, 0x82a, 0x82b, 0x82c, 0x842, 0x843 #ifndef PACKETVER #define PACKETVER 20081126 @@ -348,6 +349,8 @@ struct mmo_charstatus { #endif bool show_equip; short rename; + + time_t delete_date; }; typedef enum mail_status { diff --git a/src/login/account_sql.c b/src/login/account_sql.c index e3e725efa..be1b36ebe 100644 --- a/src/login/account_sql.c +++ b/src/login/account_sql.c @@ -12,7 +12,7 @@ #include <string.h> /// global defines -#define ACCOUNT_SQL_DB_VERSION 20080417 +#define ACCOUNT_SQL_DB_VERSION 20110114 /// internal structure typedef struct AccountDB_SQL diff --git a/src/login/login.c b/src/login/login.c index 6a8525930..6d8043baf 100644 --- a/src/login/login.c +++ b/src/login/login.c @@ -517,6 +517,7 @@ int parse_fromchar(int fd) time_t expiration_time = 0; char email[40] = ""; int gmlevel = 0; + char birthdate[10+1] = ""; int account_id = RFIFOL(fd,2); RFIFOSKIP(fd,6); @@ -528,15 +529,17 @@ int parse_fromchar(int fd) safestrncpy(email, acc.email, sizeof(email)); expiration_time = acc.expiration_time; gmlevel = acc.level; + safestrncpy(birthdate, acc.birthdate, sizeof(birthdate)); } - WFIFOHEAD(fd,51); + WFIFOHEAD(fd,62); WFIFOW(fd,0) = 0x2717; WFIFOL(fd,2) = account_id; safestrncpy((char*)WFIFOP(fd,6), email, 40); WFIFOL(fd,46) = (uint32)expiration_time; WFIFOB(fd,50) = gmlevel; - WFIFOSET(fd,51); + safestrncpy((char*)WFIFOP(fd,51), birthdate, 10+1); + WFIFOSET(fd,62); } break; |