diff options
Diffstat (limited to 'src')
41 files changed, 2810 insertions, 384 deletions
diff --git a/src/char/char.c b/src/char/char.c index 6523decf7..b499faf96 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, // + (unsigned long)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..e135fee78 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, + (unsigned long)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, (unsigned long)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 92adf8af0..39a5717ab 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 @@ -53,8 +54,6 @@ #define PACKETVER 20071106 #endif -#define FIFOSIZE_SERVERLINK 256*1024 - //Remove/Comment this line to disable sc_data saving. [Skotlex] #define ENABLE_SC_SAVING //Remove/Comment this line to disable server-side hot-key saving support [Skotlex] @@ -85,7 +84,7 @@ #define MAX_ZENY 1000000000 #define MAX_FAME 1000000000 #define MAX_CART 100 -#define MAX_SKILL 1020 +#define MAX_SKILL 2536 #define GLOBAL_REG_NUM 256 #define ACCOUNT_REG_NUM 64 #define ACCOUNT_REG2_NUM 16 @@ -95,7 +94,7 @@ #define MIN_WALK_SPEED 0 #define MAX_WALK_SPEED 1000 #define MAX_STORAGE 600 -#define MAX_GUILD_STORAGE 1000 +#define MAX_GUILD_STORAGE 600 #define MAX_PARTY 12 #define MAX_GUILD 16+10*6 // increased max guild members +6 per 1 extension levels [Lupus] #define MAX_GUILDPOSITION 20 // increased max guild positions to accomodate for all members [Valaris] (removed) [PoW] @@ -350,6 +349,8 @@ struct mmo_charstatus { #endif bool show_equip; short rename; + + time_t delete_date; }; typedef enum mail_status { diff --git a/src/common/socket.c b/src/common/socket.c index ff667cf2e..0a576e517 100644 --- a/src/common/socket.c +++ b/src/common/socket.c @@ -543,18 +543,16 @@ static int create_session(int fd, RecvFunc func_recv, SendFunc func_send, ParseF return 0; } -static int delete_session(int fd) +static void delete_session(int fd) { - if (fd <= 0 || fd >= FD_SETSIZE) - return -1; - if (session[fd]) { + if( session_isValid(fd) ) + { aFree(session[fd]->rdata); aFree(session[fd]->wdata); aFree(session[fd]->session_data); aFree(session[fd]); session[fd] = NULL; } - return 0; } int realloc_fifo(int fd, unsigned int rfifo_size, unsigned int wfifo_size) @@ -584,7 +582,7 @@ int realloc_writefifo(int fd, size_t addition) if( session[fd]->wdata_size + addition > session[fd]->max_wdata ) { // grow rule; grow in multiples of WFIFO_SIZE newsize = WFIFO_SIZE; - while( session[fd]->wdata_size + addition > newsize ) newsize += newsize; + while( session[fd]->wdata_size + addition > newsize ) newsize += WFIFO_SIZE; } else if( session[fd]->max_wdata >= (size_t)2*(session[fd]->flag.server?FIFOSIZE_SERVERLINK:WFIFO_SIZE) @@ -667,9 +665,9 @@ int WFIFOSET(int fd, size_t len) // always keep a WFIFO_SIZE reserve in the buffer // For inter-server connections, let the reserve be 1/4th of the link size. - newreserve = s->wdata_size + ( s->flag.server ? FIFOSIZE_SERVERLINK / 4 : WFIFO_SIZE); + newreserve = s->flag.server ? FIFOSIZE_SERVERLINK / 4 : WFIFO_SIZE; - // readjust the buffer to the newly chosen size + // readjust the buffer to include the chosen reserve realloc_writefifo(fd, newreserve); #ifdef SEND_SHORTLIST @@ -1333,10 +1331,12 @@ void send_shortlist_add_fd(int fd) int i; int bit; - if( fd < 0 || fd >= FD_SETSIZE ) + if( !session_isValid(fd) ) return;// out of range + i = fd/32; bit = fd%32; + if( (send_shortlist_set[i]>>bit)&1 ) return;// already in the list diff --git a/src/common/socket.h b/src/common/socket.h index 0a740a63f..2f0ec6081 100644 --- a/src/common/socket.h +++ b/src/common/socket.h @@ -9,6 +9,7 @@ #endif #ifdef WIN32 + #define WIN32_LEAN_AND_MEAN // otherwise winsock2.h includes full windows.h #include <winsock2.h> typedef long in_addr_t; #else @@ -19,6 +20,7 @@ #include <time.h> +#define FIFOSIZE_SERVERLINK 256*1024 // socket I/O macros #define RFIFOHEAD(fd) diff --git a/src/common/strlib.c b/src/common/strlib.c index a0cba906c..66f281ffc 100644 --- a/src/common/strlib.c +++ b/src/common/strlib.c @@ -8,7 +8,6 @@ #include <stdio.h> #include <stdlib.h> -#include <string.h> #include <errno.h> @@ -241,7 +240,7 @@ char* _strtok_r(char *s1, const char *s2, char **lasts) } #endif -#if !(defined(WIN32) && defined(_MSC_VER) && _MSC_VER >= 1400) && !defined(CYGWIN) +#if !(defined(WIN32) && defined(_MSC_VER) && _MSC_VER >= 1400) && !defined(HAVE_STRNLEN) /* Find the length of STRING, but scan at most MAXLEN characters. If no '\0' terminator is found in that many characters, return MAXLEN. */ size_t strnlen (const char* string, size_t maxlen) diff --git a/src/common/strlib.h b/src/common/strlib.h index 23f1e191a..3f4f984cf 100644 --- a/src/common/strlib.h +++ b/src/common/strlib.h @@ -9,6 +9,10 @@ #endif #include <stdarg.h> +#define __USE_GNU // required to enable strnlen on some platforms +#include <string.h> +#undef __USE_GNU + char* jstrescape (char* pt); char* jstrescapecpy (char* pt, const char* spt); int jmemescapecpy (char* pt, const char* spt, int size); @@ -24,7 +28,7 @@ const char *stristr(const char *haystack, const char *needle); char* _strtok_r(char* s1, const char* s2, char** lasts); #endif -#if !(defined(WIN32) && defined(_MSC_VER) && _MSC_VER >= 1400) && !defined(CYGWIN) +#if !(defined(WIN32) && defined(_MSC_VER) && _MSC_VER >= 1400) && !defined(HAVE_STRNLEN) size_t strnlen (const char* string, size_t maxlen); #endif diff --git a/src/common/utils.c b/src/common/utils.c index 12123784f..f1813ea41 100644 --- a/src/common/utils.c +++ b/src/common/utils.c @@ -5,6 +5,7 @@ #include "../common/mmo.h" #include "../common/malloc.h" #include "../common/showmsg.h" +#include "socket.h" #include "utils.h" #include <stdio.h> @@ -25,39 +26,64 @@ #include <sys/stat.h> #endif -// generate a hex dump of the first 'length' bytes of 'buffer' -void dump(FILE* fp, const unsigned char* buffer, int length) + +/// Dumps given buffer into file pointed to by a handle. +void WriteDump(FILE* fp, const void* buffer, size_t length) { - int i, j; + size_t i; + char hex[48+1], ascii[16+1]; - fprintf(fp, " Hex ASCII\n"); - fprintf(fp, " ----------------------------------------------- ----------------"); + fprintf(fp, "--- 00-01-02-03-04-05-06-07-08-09-0A-0B-0C-0D-0E-0F 0123456789ABCDEF\n"); + ascii[16] = 0; - for (i = 0; i < length; i += 16) + for( i = 0; i < length; i++ ) { - fprintf(fp, "\n%p ", &buffer[i]); - for (j = i; j < i + 16; ++j) + char c = RBUFB(buffer,i); + + ascii[i%16] = ISCNTRL(c) ? '.' : c; + sprintf(hex+(i%16)*3, "%02X ", RBUFB(buffer,i)); + + if( (i%16) == 15 ) { - if (j < length) - fprintf(fp, "%02hX ", buffer[j]); - else - fprintf(fp, " "); + fprintf(fp, "%03X %s %s\n", i/16, hex, ascii); } + } + + if( (i%16) != 0 ) + { + ascii[i%16] = 0; + fprintf(fp, "%03X %-48s %-16s\n", i/16, hex, ascii); + } +} + - fprintf(fp, " | "); +/// Dumps given buffer on the console. +void ShowDump(const void* buffer, size_t length) +{ + size_t i; + char hex[48+1], ascii[16+1]; + + ShowDebug("--- 00-01-02-03-04-05-06-07-08-09-0A-0B-0C-0D-0E-0F 0123456789ABCDEF\n"); + ascii[16] = 0; + + for( i = 0; i < length; i++ ) + { + char c = RBUFB(buffer,i); + + ascii[i%16] = ISCNTRL(c) ? '.' : c; + sprintf(hex+(i%16)*3, "%02X ", RBUFB(buffer,i)); - for (j = i; j < i + 16; ++j) + if( (i%16) == 15 ) { - if (j < length) { - if (buffer[j] > 31 && buffer[j] < 127) - fprintf(fp, "%c", buffer[j]); - else - fprintf(fp, "."); - } else - fprintf(fp, " "); + ShowDebug("%03X %s %s\n", i/16, hex, ascii); } } - fprintf(fp, "\n"); + + if( (i%16) != 0 ) + { + ascii[i%16] = 0; + ShowDebug("%03X %-48s %-16s\n", i/16, hex, ascii); + } } @@ -183,6 +209,8 @@ void findfile(const char *p, const char *pat, void (func)(const char*)) findfile(tmppath, pat, func); } }//end while + + closedir(dir); } #endif diff --git a/src/common/utils.h b/src/common/utils.h index 5cf3ff3cf..2fe078615 100644 --- a/src/common/utils.h +++ b/src/common/utils.h @@ -11,7 +11,8 @@ #include <stdio.h> // FILE* // generate a hex dump of the first 'length' bytes of 'buffer' -void dump(FILE* fp, const unsigned char* buffer, int length); +void WriteDump(FILE* fp, const void* buffer, size_t length); +void ShowDump(const void* buffer, size_t length); void findfile(const char *p, const char *pat, void (func)(const char*)); bool exists(const char* filename); 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; diff --git a/src/map/Makefile.in b/src/map/Makefile.in index cf6a29d6c..f45f4dd00 100644 --- a/src/map/Makefile.in +++ b/src/map/Makefile.in @@ -17,7 +17,8 @@ MAP_OBJ = map.o chrif.o clif.o pc.o status.o npc.o \ npc_chat.o chat.o path.o itemdb.o mob.o script.o \ storage.o skill.o atcommand.o battle.o battleground.o \ intif.o trade.o party.o vending.o guild.o pet.o \ - log.o mail.o date.o unit.o homunculus.o mercenary.o quest.o instance.o + log.o mail.o date.o unit.o homunculus.o mercenary.o quest.o instance.o \ + buyingstore.o searchstore.o MAP_TXT_OBJ = $(MAP_OBJ:%=obj_txt/%) \ obj_txt/mapreg_txt.o MAP_SQL_OBJ = $(MAP_OBJ:%=obj_sql/%) \ @@ -26,7 +27,8 @@ MAP_H = map.h chrif.h clif.h pc.h status.h npc.h \ chat.h itemdb.h mob.h script.h path.h \ storage.h skill.h atcommand.h battle.h battleground.h \ intif.h trade.h party.h vending.h guild.h pet.h \ - log.h mail.h date.h unit.h homunculus.h mercenary.h quest.h instance.h mapreg.h + log.h mail.h date.h unit.h homunculus.h mercenary.h quest.h instance.h mapreg.h \ + buyingstore.h searchstore.h HAVE_MYSQL=@HAVE_MYSQL@ ifeq ($(HAVE_MYSQL),yes) diff --git a/src/map/atcommand.c b/src/map/atcommand.c index cebd881ce..b44eae946 100644 --- a/src/map/atcommand.c +++ b/src/map/atcommand.c @@ -1150,7 +1150,7 @@ ACMD_FUNC(storage) { nullpo_retr(-1, sd); - if (sd->npc_id || sd->vender_id || sd->state.trading || sd->state.storage_flag) + if (sd->npc_id || sd->vender_id || sd->state.buyingstore || sd->state.trading || sd->state.storage_flag) return -1; if (storage_storageopen(sd) == 1) @@ -1177,7 +1177,7 @@ ACMD_FUNC(guildstorage) return -1; } - if (sd->npc_id || sd->vender_id || sd->state.trading) + if (sd->npc_id || sd->vender_id || sd->state.buyingstore || sd->state.trading) return -1; if (sd->state.storage_flag == 1) { @@ -1673,8 +1673,8 @@ ACMD_FUNC(item2) } if (item_data->type == IT_PETARMOR) refine = 0; - if (refine > 10) - refine = 10; + if (refine > MAX_REFINE) + refine = MAX_REFINE; } else { identify = 1; refine = attr = 0; @@ -1956,7 +1956,7 @@ ACMD_FUNC(pvpoff) map[sd->bl.m].flag.pvp = 0; if (!battle_config.pk_mode) - clif_send0199(sd->bl.m, 0); + clif_map_property_mapall(sd->bl.m, MAPPROPERTY_NOTHING); map_foreachinmap(atcommand_pvpoff_sub,sd->bl.m, BL_PC); map_foreachinmap(atcommand_stopattack,sd->bl.m, BL_CHAR, 0); clif_displaymessage(fd, msg_txt(31)); // PvP: Off. @@ -1993,7 +1993,7 @@ ACMD_FUNC(pvpon) if (!battle_config.pk_mode) {// display pvp circle and rank - clif_send0199(sd->bl.m, 1); + clif_map_property_mapall(sd->bl.m, MAPPROPERTY_FREEPVPZONE); map_foreachinmap(atcommand_pvpon_sub,sd->bl.m, BL_PC); } @@ -2015,7 +2015,7 @@ ACMD_FUNC(gvgoff) } map[sd->bl.m].flag.gvg = 0; - clif_send0199(sd->bl.m, 0); + clif_map_property_mapall(sd->bl.m, MAPPROPERTY_NOTHING); map_foreachinmap(atcommand_stopattack,sd->bl.m, BL_CHAR, 0); clif_displaymessage(fd, msg_txt(33)); // GvG: Off. @@ -2035,7 +2035,7 @@ ACMD_FUNC(gvgon) } map[sd->bl.m].flag.gvg = 1; - clif_send0199(sd->bl.m, 3); + clif_map_property_mapall(sd->bl.m, MAPPROPERTY_AGITZONE); clif_displaymessage(fd, msg_txt(34)); // GvG: On. return 0; @@ -3009,7 +3009,7 @@ ACMD_FUNC(param) status[4] = &sd->status.dex; status[5] = &sd->status.luk; - if(value < 0 && *status[i] < -value) + if(value < 0 && *status[i] <= -value) { new_value = 1; } @@ -5945,8 +5945,8 @@ ACMD_FUNC(autotrade) return -1; } - if( !sd->vender_id ) { //check if player is vending - clif_displaymessage(fd, msg_txt(549)); // You should be vending to use @Autotrade. + if( !sd->vender_id && !sd->state.buyingstore ) { //check if player is vending or buying + clif_displaymessage(fd, msg_txt(549)); // "You should have a shop open to use @autotrade." return -1; } diff --git a/src/map/battle.c b/src/map/battle.c index 3e58c1209..f60be479a 100644 --- a/src/map/battle.c +++ b/src/map/battle.c @@ -4006,6 +4006,12 @@ static const struct _battle_data { { "autospell_check_range", &battle_config.autospell_check_range, 0, 0, 1, }, { "client_reshuffle_dice", &battle_config.client_reshuffle_dice, 0, 0, 1, }, { "client_sort_storage", &battle_config.client_sort_storage, 0, 0, 1, }, + { "gm_check_minlevel", &battle_config.gm_check_minlevel, 60, 0, 100, }, + { "feature.buying_store", &battle_config.feature_buying_store, 1, 0, 1, }, + { "feature.search_stores", &battle_config.feature_search_stores, 1, 0, 1, }, + { "searchstore_querydelay", &battle_config.searchstore_querydelay, 10, 0, INT_MAX, }, + { "searchstore_maxresults", &battle_config.searchstore_maxresults, 30, 1, INT_MAX, }, + { "display_party_name", &battle_config.display_party_name, 0, 0, 1, }, // BattleGround Settings { "bg_update_interval", &battle_config.bg_update_interval, 1000, 100, INT_MAX, }, { "bg_short_attack_damage_rate", &battle_config.bg_short_damage_rate, 80, 0, INT_MAX, }, diff --git a/src/map/battle.h b/src/map/battle.h index 3abc6b0a4..b2f33b73b 100644 --- a/src/map/battle.h +++ b/src/map/battle.h @@ -480,6 +480,12 @@ extern struct Battle_Config int autospell_check_range; //Enable range check for autospell bonus. [L0ne_W0lf] int client_reshuffle_dice; // Reshuffle /dice int client_sort_storage; + int gm_check_minlevel; // min GM level for /check + int feature_buying_store; + int feature_search_stores; + int searchstore_querydelay; + int searchstore_maxresults; + int display_party_name; // [BattleGround Settings] int bg_update_interval; diff --git a/src/map/buyingstore.c b/src/map/buyingstore.c new file mode 100644 index 000000000..8f158cd29 --- /dev/null +++ b/src/map/buyingstore.c @@ -0,0 +1,470 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/db.h" // ARR_FIND +#include "../common/showmsg.h" // ShowWarning +#include "../common/socket.h" // RBUF* +#include "../common/strlib.h" // safestrncpy +#include "atcommand.h" // msg_txt +#include "battle.h" // battle_config.* +#include "buyingstore.h" // struct s_buyingstore +#include "clif.h" // clif_buyingstore_* +#include "log.h" // log_pick_pc, log_zeny +#include "pc.h" // struct map_session_data + + +/// constants (client-side restrictions) +#define BUYINGSTORE_MAX_PRICE 99990000 +#define BUYINGSTORE_MAX_AMOUNT 9999 + + +/// failure constants for clif functions +enum e_buyingstore_failure +{ + BUYINGSTORE_CREATE = 1, // "Failed to open buying store." + BUYINGSTORE_CREATE_OVERWEIGHT = 2, // "Total amount of then possessed items exceeds the weight limit by %d. Please re-enter." + BUYINGSTORE_TRADE_BUYER_ZENY = 3, // "All items within the buy limit were purchased." + BUYINGSTORE_TRADE_BUYER_NO_ITEMS = 4, // "All items were purchased." + BUYINGSTORE_TRADE_SELLER_FAILED = 5, // "The deal has failed." + BUYINGSTORE_TRADE_SELLER_COUNT = 6, // "The trade failed, because the entered amount of item %s is higher, than the buyer is willing to buy." + BUYINGSTORE_TRADE_SELLER_ZENY = 7, // "The trade failed, because the buyer is lacking required balance." + BUYINGSTORE_CREATE_NO_INFO = 8, // "No sale (purchase) information available." +}; + + +static unsigned int buyingstore_nextid = 0; +static const short buyingstore_blankslots[MAX_SLOTS] = { 0 }; // used when checking whether or not an item's card slots are blank + + +/// Returns unique buying store id +static unsigned int buyingstore_getuid(void) +{ + return buyingstore_nextid++; +} + + +bool buyingstore_setup(struct map_session_data* sd, unsigned char slots) +{ + if( !battle_config.feature_buying_store || sd->vender_id || sd->state.buyingstore || sd->state.trading || slots == 0 ) + { + return false; + } + + if( sd->sc.data[SC_NOCHAT] && (sd->sc.data[SC_NOCHAT]->val1&MANNER_NOROOM) ) + {// custom: mute limitation + return false; + } + + if( map[sd->bl.m].flag.novending || map_getcell(sd->bl.m, sd->bl.x, sd->bl.y, CELL_CHKNOVENDING) ) + {// custom: no vending maps/cells + clif_displaymessage(sd->fd, msg_txt(276)); // "You can't open a shop on this map" + return false; + } + + if( slots > MAX_BUYINGSTORE_SLOTS ) + { + ShowWarning("buyingstore_setup: Requested %d slots, but server supports only %d slots.\n", (int)slots, MAX_BUYINGSTORE_SLOTS); + slots = MAX_BUYINGSTORE_SLOTS; + } + + sd->buyingstore.slots = slots; + clif_buyingstore_open(sd); + + return true; +} + + +void buyingstore_create(struct map_session_data* sd, int zenylimit, unsigned char result, const char* storename, const uint8* itemlist, unsigned int count) +{ + unsigned int i, weight, listidx; + struct item_data* id; + + if( !result || count == 0 ) + {// canceled, or no items + return; + } + + if( !battle_config.feature_buying_store || pc_istrading(sd) || sd->buyingstore.slots == 0 || count > sd->buyingstore.slots || zenylimit <= 0 || zenylimit > sd->status.zeny || !storename[0] ) + {// disabled or invalid input + sd->buyingstore.slots = 0; + clif_buyingstore_open_failed(sd, BUYINGSTORE_CREATE, 0); + return; + } + + if( !pc_can_give_items(pc_isGM(sd)) ) + {// custom: GM is not allowed to buy (give zeny) + sd->buyingstore.slots = 0; + clif_displaymessage(sd->fd, msg_txt(246)); + clif_buyingstore_open_failed(sd, BUYINGSTORE_CREATE, 0); + return; + } + + if( sd->sc.data[SC_NOCHAT] && (sd->sc.data[SC_NOCHAT]->val1&MANNER_NOROOM) ) + {// custom: mute limitation + return; + } + + if( map[sd->bl.m].flag.novending || map_getcell(sd->bl.m, sd->bl.x, sd->bl.y, CELL_CHKNOVENDING) ) + {// custom: no vending maps/cells + clif_displaymessage(sd->fd, msg_txt(276)); // "You can't open a shop on this map" + return; + } + + weight = sd->weight; + + // check item list + for( i = 0; i < count; i++ ) + {// itemlist: <name id>.W <amount>.W <price>.L + unsigned short nameid, amount; + int price, idx; + + nameid = RBUFW(itemlist,i*8+0); + amount = RBUFW(itemlist,i*8+2); + price = RBUFL(itemlist,i*8+4); + + if( ( id = itemdb_exists(nameid) ) == NULL || amount == 0 ) + {// invalid input + break; + } + + if( price <= 0 || price > BUYINGSTORE_MAX_PRICE ) + {// invalid price: unlike vending, items cannot be bought at 0 Zeny + break; + } + + if( !id->flag.buyingstore || !itemdb_cantrade_sub(id, pc_isGM(sd), pc_isGM(sd)) || ( idx = pc_search_inventory(sd, nameid) ) == -1 ) + {// restrictions: allowed, no character-bound items and at least one must be owned + break; + } + + if( sd->status.inventory[idx].amount+amount > BUYINGSTORE_MAX_AMOUNT ) + {// too many items of same kind + break; + } + + if( i ) + {// duplicate check. as the client does this too, only malicious intent should be caught here + ARR_FIND( 0, i, listidx, sd->buyingstore.items[i].nameid == nameid ); + if( listidx != i ) + {// duplicate + ShowWarning("buyingstore_create: Found duplicate item on buying list (nameid=%hu, amount=%hu, account_id=%d, char_id=%d).\n", nameid, amount, sd->status.account_id, sd->status.char_id); + break; + } + } + + weight+= id->weight*amount; + sd->buyingstore.items[i].nameid = nameid; + sd->buyingstore.items[i].amount = amount; + sd->buyingstore.items[i].price = price; + } + + if( i != count ) + {// invalid item/amount/price + sd->buyingstore.slots = 0; + clif_buyingstore_open_failed(sd, BUYINGSTORE_CREATE, 0); + return; + } + + if( (sd->max_weight*90)/100 < weight ) + {// not able to carry all wanted items without getting overweight (90%) + sd->buyingstore.slots = 0; + clif_buyingstore_open_failed(sd, BUYINGSTORE_CREATE_OVERWEIGHT, weight); + return; + } + + // success + sd->state.buyingstore = true; + sd->buyer_id = buyingstore_getuid(); + sd->buyingstore.zenylimit = zenylimit; + sd->buyingstore.slots = i; // store actual amount of items + safestrncpy(sd->message, storename, sizeof(sd->message)); + clif_buyingstore_myitemlist(sd); + clif_buyingstore_entry(sd); +} + + +void buyingstore_close(struct map_session_data* sd) +{ + if( sd->state.buyingstore ) + { + // invalidate data + sd->state.buyingstore = false; + memset(&sd->buyingstore, 0, sizeof(sd->buyingstore)); + + // notify other players + clif_buyingstore_disappear_entry(sd); + } +} + + +void buyingstore_open(struct map_session_data* sd, int account_id) +{ + struct map_session_data* pl_sd; + + if( !battle_config.feature_buying_store || pc_istrading(sd) ) + {// not allowed to sell + return; + } + + if( !pc_can_give_items(pc_isGM(sd)) ) + {// custom: GM is not allowed to sell + clif_displaymessage(sd->fd, msg_txt(246)); + return; + } + + if( ( pl_sd = map_id2sd(account_id) ) == NULL || !pl_sd->state.buyingstore ) + {// not online or not buying + return; + } + + if( !searchstore_queryremote(sd, account_id) && ( sd->bl.m != pl_sd->bl.m || !check_distance_bl(&sd->bl, &pl_sd->bl, AREA_SIZE) ) ) + {// out of view range + return; + } + + // success + clif_buyingstore_itemlist(sd, pl_sd); +} + + +void buyingstore_trade(struct map_session_data* sd, int account_id, unsigned int buyer_id, const uint8* itemlist, unsigned int count) +{ + int zeny = 0; + unsigned int i, weight, listidx, k; + struct map_session_data* pl_sd; + + if( count == 0 ) + {// nothing to do + return; + } + + if( !battle_config.feature_buying_store || pc_istrading(sd) ) + {// not allowed to sell + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0); + return; + } + + if( !pc_can_give_items(pc_isGM(sd)) ) + {// custom: GM is not allowed to sell + clif_displaymessage(sd->fd, msg_txt(246)); + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0); + return; + } + + if( ( pl_sd = map_id2sd(account_id) ) == NULL || !pl_sd->state.buyingstore || pl_sd->buyer_id != buyer_id ) + {// not online, not buying or not same store + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0); + return; + } + + if( !searchstore_queryremote(sd, account_id) && ( sd->bl.m != pl_sd->bl.m || !check_distance_bl(&sd->bl, &pl_sd->bl, AREA_SIZE) ) ) + {// out of view range + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, 0); + return; + } + + searchstore_clearremote(sd); + + if( pl_sd->status.zeny < pl_sd->buyingstore.zenylimit ) + {// buyer lost zeny in the mean time? fix the limit + pl_sd->buyingstore.zenylimit = pl_sd->status.zeny; + } + weight = pl_sd->weight; + + // check item list + for( i = 0; i < count; i++ ) + {// itemlist: <index>.W <name id>.W <amount>.W + unsigned short nameid, amount; + int index; + + index = RBUFW(itemlist,i*6+0)-2; + nameid = RBUFW(itemlist,i*6+2); + amount = RBUFW(itemlist,i*6+4); + + if( i ) + {// duplicate check. as the client does this too, only malicious intent should be caught here + ARR_FIND( 0, i, k, RBUFW(itemlist,k*6+0)-2 == index ); + if( k != i ) + {// duplicate + ShowWarning("buyingstore_trade: Found duplicate item on selling list (prevnameid=%hu, prevamount=%hu, nameid=%hu, amount=%hu, account_id=%d, char_id=%d).\n", + RBUFW(itemlist,k*6+2), RBUFW(itemlist,k*6+4), nameid, amount, sd->status.account_id, sd->status.char_id); + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); + return; + } + } + + if( index < 0 || index >= ARRAYLENGTH(sd->status.inventory) || sd->inventory_data[index] == NULL || sd->status.inventory[index].nameid != nameid || sd->status.inventory[index].amount < amount ) + {// invalid input + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); + return; + } + + if( sd->status.inventory[index].expire_time || !itemdb_cantrade(&sd->status.inventory[index], pc_isGM(sd), pc_isGM(pl_sd)) || memcmp(sd->status.inventory[index].card, buyingstore_blankslots, sizeof(buyingstore_blankslots)) ) + {// non-tradable item + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); + return; + } + + ARR_FIND( 0, pl_sd->buyingstore.slots, listidx, pl_sd->buyingstore.items[listidx].nameid == nameid ); + if( listidx == pl_sd->buyingstore.slots || pl_sd->buyingstore.items[listidx].amount == 0 ) + {// there is no such item or the buyer has already bought all of them + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); + return; + } + + if( pl_sd->buyingstore.items[listidx].amount < amount ) + {// buyer does not need that much of the item + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_COUNT, nameid); + return; + } + + if( pc_checkadditem(pl_sd, nameid, amount) == ADDITEM_OVERAMOUNT ) + {// buyer does not have enough space for this item + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); + return; + } + + if( amount*(unsigned int)sd->inventory_data[index]->weight > pl_sd->max_weight-weight ) + {// normally this is not supposed to happen, as the total weight is + // checked upon creation, but the buyer could have gained items + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_FAILED, nameid); + return; + } + weight+= amount*sd->inventory_data[index]->weight; + + if( amount*pl_sd->buyingstore.items[listidx].price > pl_sd->buyingstore.zenylimit-zeny ) + {// buyer does not have enough zeny + clif_buyingstore_trade_failed_seller(sd, BUYINGSTORE_TRADE_SELLER_ZENY, nameid); + return; + } + zeny+= amount*pl_sd->buyingstore.items[listidx].price; + } + + // process item list + for( i = 0; i < count; i++ ) + {// itemlist: <index>.W <name id>.W <amount>.W + unsigned short nameid, amount; + int index; + + index = RBUFW(itemlist,i*6+0)-2; + nameid = RBUFW(itemlist,i*6+2); + amount = RBUFW(itemlist,i*6+4); + + ARR_FIND( 0, pl_sd->buyingstore.slots, listidx, pl_sd->buyingstore.items[listidx].nameid == nameid ); + zeny = amount*pl_sd->buyingstore.items[listidx].price; + + // log + if( log_config.enable_logs&LOG_BUYING_STORE ) + { + log_pick_pc(sd, "B", nameid, -((int)amount), &sd->status.inventory[index]); + log_pick_pc(pl_sd, "B", nameid, amount, &sd->status.inventory[index]); + } + if( log_config.zeny ) + log_zeny(sd, "B", pl_sd, zeny); + + // move item + pc_additem(pl_sd, &sd->status.inventory[index], amount); + pc_delitem(sd, index, amount, 1, 0); + pl_sd->buyingstore.items[listidx].amount-= amount; + + // pay up + pc_payzeny(pl_sd, zeny); + pc_getzeny(sd, zeny); + pl_sd->buyingstore.zenylimit-= zeny; + + // notify clients + clif_buyingstore_delete_item(sd, index, amount, pl_sd->buyingstore.items[listidx].price); + clif_buyingstore_update_item(pl_sd, nameid, amount); + } + + // check whether or not there is still something to buy + ARR_FIND( 0, pl_sd->buyingstore.slots, i, pl_sd->buyingstore.items[i].amount != 0 ); + if( i == pl_sd->buyingstore.slots ) + {// everything was bought + clif_buyingstore_trade_failed_buyer(pl_sd, BUYINGSTORE_TRADE_BUYER_NO_ITEMS); + } + else if( pl_sd->buyingstore.zenylimit == 0 ) + {// zeny limit reached + clif_buyingstore_trade_failed_buyer(pl_sd, BUYINGSTORE_TRADE_BUYER_ZENY); + } + else + {// continue buying + return; + } + + // cannot continue buying + buyingstore_close(pl_sd); + + // remove auto-trader + if( pl_sd->state.autotrade ) + { + map_quit(pl_sd); + } +} + + +/// Checks if an item is being bought in given player's buying store. +bool buyingstore_search(struct map_session_data* sd, unsigned short nameid) +{ + unsigned int i; + + if( !sd->state.buyingstore ) + {// not buying + return false; + } + + ARR_FIND( 0, sd->buyingstore.slots, i, sd->buyingstore.items[i].nameid == nameid && sd->buyingstore.items[i].amount ); + if( i == sd->buyingstore.slots ) + {// not found + return false; + } + + return true; +} + + +/// Searches for all items in a buyingstore, that match given ids, price and possible cards. +/// @return Whether or not the search should be continued. +bool buyingstore_searchall(struct map_session_data* sd, const struct s_search_store_search* s) +{ + unsigned int i, idx; + struct s_buyingstore_item* it; + + if( !sd->state.buyingstore ) + {// not buying + return true; + } + + for( idx = 0; idx < s->item_count; idx++ ) + { + ARR_FIND( 0, sd->buyingstore.slots, i, sd->buyingstore.items[i].nameid == s->itemlist[idx] && sd->buyingstore.items[i].amount ); + if( i == sd->buyingstore.slots ) + {// not found + continue; + } + it = &sd->buyingstore.items[i]; + + if( s->min_price && s->min_price > (unsigned int)it->price ) + {// too low price + continue; + } + + if( s->max_price && s->max_price < (unsigned int)it->price ) + {// too high price + continue; + } + + if( s->card_count ) + {// ignore cards, as there cannot be any + ; + } + + if( !searchstore_result(s->search_sd, sd->buyer_id, sd->status.account_id, sd->message, it->nameid, it->amount, it->price, buyingstore_blankslots, 0) ) + {// result set full + return false; + } + } + + return true; +} diff --git a/src/map/buyingstore.h b/src/map/buyingstore.h new file mode 100644 index 000000000..0ed6e5457 --- /dev/null +++ b/src/map/buyingstore.h @@ -0,0 +1,33 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _BUYINGSTORE_H_ +#define _BUYINGSTORE_H_ + +struct s_search_store_search; + +#define MAX_BUYINGSTORE_SLOTS 5 + +struct s_buyingstore_item +{ + int price; + unsigned short amount; + unsigned short nameid; +}; + +struct s_buyingstore +{ + struct s_buyingstore_item items[MAX_BUYINGSTORE_SLOTS]; + int zenylimit; + unsigned char slots; +}; + +bool buyingstore_setup(struct map_session_data* sd, unsigned char slots); +void buyingstore_create(struct map_session_data* sd, int zenylimit, unsigned char result, const char* storename, const uint8* itemlist, unsigned int count); +void buyingstore_close(struct map_session_data* sd); +void buyingstore_open(struct map_session_data* sd, int account_id); +void buyingstore_trade(struct map_session_data* sd, int account_id, unsigned int buyer_id, const uint8* itemlist, unsigned int count); +bool buyingstore_search(struct map_session_data* sd, unsigned short nameid); +bool buyingstore_searchall(struct map_session_data* sd, const struct s_search_store_search* s); + +#endif // _BUYINGSTORE_H_ diff --git a/src/map/chat.c b/src/map/chat.c index 96ec4c20d..4ba784b2e 100644 --- a/src/map/chat.c +++ b/src/map/chat.c @@ -69,6 +69,11 @@ int chat_createpcchat(struct map_session_data* sd, const char* title, const char if( sd->chatID ) return 0; //Prevent people abusing the chat system by creating multiple chats, as pointed out by End of Exam. [Skotlex] + if( sd->vender_id || sd->state.buyingstore ) + {// not chat, when you already have a store open + return 0; + } + if( map[sd->bl.m].flag.nochat ) { clif_displaymessage(sd->fd, msg_txt(281)); @@ -108,7 +113,7 @@ int chat_joinchat(struct map_session_data* sd, int chatid, const char* pass) nullpo_ret(sd); cd = (struct chat_data*)map_id2bl(chatid); - if( cd == NULL || cd->bl.type != BL_CHAT || cd->bl.m != sd->bl.m || sd->vender_id || sd->chatID || cd->users >= cd->limit ) + if( cd == NULL || cd->bl.type != BL_CHAT || cd->bl.m != sd->bl.m || sd->vender_id || sd->state.buyingstore || sd->chatID || cd->users >= cd->limit ) { clif_joinchatfail(sd,0); return 0; diff --git a/src/map/clif.c b/src/map/clif.c index b724ddbab..960a63c0e 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -46,7 +46,8 @@ #include <stdarg.h> #include <time.h> -#define DUMP_UNKNOWN_PACKET 0 +//#define DUMP_UNKNOWN_PACKET +//#define DUMP_INVALID_PACKET struct Clif_Config { int packet_db_ver; //Preferred packet version. @@ -56,7 +57,10 @@ struct Clif_Config { struct s_packet_db packet_db[MAX_PACKET_VER + 1][MAX_PACKET_DB + 1]; //Converts item type in case of pet eggs. -#define itemtype(a) (a == IT_PETEGG)?IT_WEAPON:a +inline int itemtype(int type) +{ + return ( type == IT_PETEGG ) ? IT_WEAPON : type; +} #define WBUFPOS(p,pos,x,y,dir) \ do { \ @@ -1216,7 +1220,7 @@ int clif_hominfo(struct map_session_data *sd, struct homun_data *hd, int flag) WBUFL(buf,59)=hd->homunculus.exp; WBUFL(buf,63)=hd->exp_next; WBUFW(buf,67)=hd->homunculus.skillpts; - WBUFW(buf,69)=2; // FIXME: undocumented flag, seems to be '2' all the time [ultramage] + WBUFW(buf,69)=status_get_range(&hd->bl); clif_send(buf,packet_len(0x22e),&sd->bl,SELF); return 0; } @@ -1227,7 +1231,7 @@ void clif_send_homdata(struct map_session_data *sd, int type, int param) WFIFOHEAD(fd, packet_len(0x230)); nullpo_retv(sd->hd); WFIFOW(fd,0)=0x230; - WFIFOW(fd,2)=type; + WFIFOW(fd,2)=type; // FIXME: This is actually <type>.B <state>.B WFIFOL(fd,4)=sd->hd->bl.id; WFIFOL(fd,8)=param; WFIFOSET(fd,packet_len(0x230)); @@ -2815,8 +2819,8 @@ int clif_initialstatus(struct map_session_data *sd) WBUFW(buf,34) = sd->battle_status.flee; WBUFW(buf,36) = sd->battle_status.flee2/10; WBUFW(buf,38) = sd->battle_status.cri/10; - WBUFW(buf,40) = sd->status.karma; - WBUFW(buf,42) = sd->status.manner; + WBUFW(buf,40) = sd->battle_status.amotion; // aspd + WBUFW(buf,42) = sd->status.manner; // FIXME: This is 'plusASPD', but what is it supposed to be? WFIFOSET(fd,packet_len(0xbd)); @@ -3644,6 +3648,9 @@ static void clif_getareachar_pc(struct map_session_data* sd,struct map_session_d if(dstsd->vender_id) clif_showvendingboard(&dstsd->bl,dstsd->message,sd->fd); + if( dstsd->state.buyingstore ) + clif_buyingstore_entry_single(sd, dstsd); + if(dstsd->spiritball > 0) clif_spiritball_single(sd->fd, dstsd); @@ -4072,6 +4079,8 @@ int clif_outsight(struct block_list *bl,va_list ap) } if(sd->vender_id) clif_closevendingboard(bl,tsd->fd); + if( sd->state.buyingstore ) + clif_buyingstore_disappear_entry_single(tsd, sd); break; case BL_ITEM: clif_clearflooritem((struct flooritem_data*)bl,tsd->fd); @@ -4129,42 +4138,6 @@ int clif_insight(struct block_list *bl,va_list ap) } /*========================================== - * - *------------------------------------------ -int clif_skillinfo(struct map_session_data *sd,int skillid,int type,int range) -{ - int fd,id; - - nullpo_ret(sd); - - fd=sd->fd; - if( (id=sd->status.skill[skillid].id) <= 0 ) - return 0; - WFIFOHEAD(fd,packet_len(0x147)); - WFIFOW(fd,0)=0x147; - WFIFOW(fd,2) = id; - if(type < 0) - WFIFOW(fd,4) = skill_get_inf(id); - else - WFIFOW(fd,4) = type; - WFIFOW(fd,6) = 0; - WFIFOW(fd,8) = sd->status.skill[skillid].lv; - WFIFOW(fd,10) = skill_get_sp(id,sd->status.skill[skillid].lv); - if(range < 0) - range = skill_get_range2(&sd->bl, id,sd->status.skill[skillid].lv); - - WFIFOW(fd,12)= range; - safestrncpy((char*)WFIFOP(fd,14), skill_get_name(id), NAME_LENGTH); - if(sd->status.skill[skillid].flag ==0) - WFIFOB(fd,38)= (sd->status.skill[skillid].lv < skill_tree_get_max(id, sd->status.class_))? 1:0; - else - WFIFOB(fd,38) = 0; - WFIFOSET(fd,packet_len(0x147)); - - return 0; -}*/ - -/*========================================== * スキルリストを送信する *------------------------------------------*/ int clif_skillinfoblock(struct map_session_data *sd) @@ -4390,7 +4363,7 @@ int clif_skill_fail(struct map_session_data *sd,int skill_id,int type,int btype) WFIFOW(fd,0) = 0x110; WFIFOW(fd,2) = skill_id; WFIFOL(fd,4) = btype; - WFIFOB(fd,8) = 0;// success? + WFIFOB(fd,8) = 0;// success WFIFOB(fd,9) = type; WFIFOSET(fd,packet_len(0x110)); @@ -4966,6 +4939,12 @@ void clif_GlobalMessage(struct block_list* bl, const char* message) len = strlen(message)+1; + if( len > sizeof(buf)-8 ) + { + ShowWarning("clif_GlobalMessage: Truncating too long message '%s' (len=%d).\n", message, len); + len = sizeof(buf)-8; + } + WBUFW(buf,0)=0x8d; WBUFW(buf,2)=len+8; WBUFL(buf,4)=bl->id; @@ -5057,15 +5036,8 @@ int clif_resurrection(struct block_list *bl,int type) return 0; } -/// Sets the map mode. -/// -/// mode=1 : pvp mode -/// mode=2 : unknown mode (pk?) -/// mode=3 : gvg mode -/// mode=4 : message "You are in a PK area. Please beware of sudden attacks." in color 0x9B9BFF (light red) -/// mode=5 : pvp mode -/// mode=other : ? -void clif_set0199(struct map_session_data* sd, int mode) +/// Sets the map property (ZC_NOTIFY_MAPPROPERTY). +void clif_map_property(struct map_session_data* sd, enum map_property property) { int fd; @@ -5074,13 +5046,12 @@ void clif_set0199(struct map_session_data* sd, int mode) fd=sd->fd; WFIFOHEAD(fd,packet_len(0x199)); WFIFOW(fd,0)=0x199; - WFIFOW(fd,2)=mode; + WFIFOW(fd,2)=property; WFIFOSET(fd,packet_len(0x199)); } -/// Set the map mode (special) -/// 19 = battleground -void clif_set01D6(struct map_session_data* sd, int mode) +/// Set the map type (ZC_NOTIFY_MAPPROPERTY2) +void clif_map_type(struct map_session_data* sd, enum map_type type) { int fd; @@ -5089,7 +5060,7 @@ void clif_set01D6(struct map_session_data* sd, int mode) fd=sd->fd; WFIFOHEAD(fd,packet_len(0x1D6)); WFIFOW(fd,0)=0x1D6; - WFIFOW(fd,2)=mode; + WFIFOW(fd,2)=type; WFIFOSET(fd,packet_len(0x1D6)); } @@ -5129,7 +5100,7 @@ int clif_pvpset(struct map_session_data *sd,int pvprank,int pvpnum,int type) /*========================================== * *------------------------------------------*/ -int clif_send0199(int map,int type) +void clif_map_property_mapall(int map, enum map_property property) { struct block_list bl; unsigned char buf[16]; @@ -5138,10 +5109,8 @@ int clif_send0199(int map,int type) bl.type = BL_NUL; bl.m = map; WBUFW(buf,0)=0x199; - WBUFW(buf,2)=type; + WBUFW(buf,2)=property; clif_send(buf,packet_len(0x199),&bl,ALL_SAMEMAP); - - return 0; } /*========================================== @@ -5189,7 +5158,7 @@ int clif_wis_message(int fd, const char* nick, const char* mes, int mes_len) WFIFOW(fd,0) = 0x97; WFIFOW(fd,2) = mes_len + NAME_LENGTH + 8; safestrncpy((char*)WFIFOP(fd,4), nick, NAME_LENGTH); - WFIFOL(fd,28) = 0; // unknown; if nonzero, also displays text above char + WFIFOL(fd,28) = 0; // isAdmin; if nonzero, also displays text above char safestrncpy((char*)WFIFOP(fd,32), mes, mes_len); WFIFOSET(fd,WFIFOW(fd,2)); #endif @@ -5849,7 +5818,7 @@ int clif_party_invite(struct map_session_data *sd,struct map_session_data *tsd) WFIFOHEAD(fd,packet_len(0xfe)); WFIFOW(fd,0)=0xfe; - WFIFOL(fd,2)=sd->status.account_id; + WFIFOL(fd,2)=sd->status.account_id; // FIXME: This is party_id memcpy(WFIFOP(fd,6),p->party.name,NAME_LENGTH); WFIFOSET(fd,packet_len(0xfe)); return 0; @@ -6240,15 +6209,13 @@ int clif_sendegg(struct map_session_data *sd) } WFIFOHEAD(fd, MAX_INVENTORY * 2 + 4); WFIFOW(fd,0)=0x1a6; - if(sd->status.pet_id <= 0) { - for(i=0,n=0;i<MAX_INVENTORY;i++){ - if(sd->status.inventory[i].nameid<=0 || sd->inventory_data[i] == NULL || - sd->inventory_data[i]->type!=IT_PETEGG || - sd->status.inventory[i].amount<=0) - continue; - WFIFOW(fd,n*2+4)=i+2; - n++; - } + for(i=0,n=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid<=0 || sd->inventory_data[i] == NULL || + sd->inventory_data[i]->type!=IT_PETEGG || + sd->status.inventory[i].amount<=0) + continue; + WFIFOW(fd,n*2+4)=i+2; + n++; } WFIFOW(fd,2)=4+n*2; WFIFOSET(fd,WFIFOW(fd,2)); @@ -7028,14 +6995,21 @@ int clif_guild_leave(struct map_session_data *sd,const char *name,const char *me int clif_guild_expulsion(struct map_session_data *sd,const char *name,const char *mes,int account_id) { unsigned char buf[128]; +#if PACKETVER < 20100803 + const unsigned short cmd = 0x15c; +#else + const unsigned short cmd = 0x839; +#endif nullpo_ret(sd); - WBUFW(buf, 0)=0x15c; + WBUFW(buf,0) = cmd; safestrncpy((char*)WBUFP(buf, 2),name,NAME_LENGTH); safestrncpy((char*)WBUFP(buf,26),mes,40); +#if PACKETVER < 20100803 safestrncpy((char*)WBUFP(buf,66),"",NAME_LENGTH); // account name (not used for security reasons) - clif_send(buf,packet_len(0x15c),&sd->bl,GUILD_NOBG); +#endif + clif_send(buf,packet_len(cmd),&sd->bl,GUILD_NOBG); return 0; } @@ -7561,6 +7535,12 @@ int clif_messagecolor(struct block_list* bl, unsigned long color, const char* ms nullpo_ret(bl); + if( msg_len > sizeof(buf)-12 ) + { + ShowWarning("clif_messagecolor: Truncating too long message '%s' (len=%u).\n", msg, msg_len); + msg_len = sizeof(buf)-12; + } + WBUFW(buf,0) = 0x2C1; WBUFW(buf,2) = msg_len + 12; WBUFL(buf,4) = bl->id; @@ -7580,6 +7560,12 @@ int clif_message(struct block_list* bl, const char* msg) nullpo_ret(bl); + if( msg_len > sizeof(buf)-8 ) + { + ShowWarning("clif_message: Truncating too long message '%s' (len=%u).\n", msg, msg_len); + msg_len = sizeof(buf)-8; + } + WBUFW(buf,0) = 0x8d; WBUFW(buf,2) = msg_len + 8; WBUFL(buf,4) = bl->id; @@ -7667,8 +7653,13 @@ int clif_charnameack (int fd, struct block_list *bl) } memcpy(WBUFP(buf,6), ssd->status.name, NAME_LENGTH); - if (ssd->status.party_id > 0) - p = party_search(ssd->status.party_id); + if (!battle_config.display_party_name) { + if (ssd->status.party_id > 0 && ssd->status.guild_id > 0 && (g = guild_search(ssd->status.guild_id)) != NULL) + p = party_search(ssd->status.party_id); + }else{ + if (ssd->status.party_id > 0) + p = party_search(ssd->status.party_id); + } if( ssd->status.guild_id > 0 && (g = guild_search(ssd->status.guild_id)) != NULL ) { @@ -7783,8 +7774,13 @@ int clif_charnameupdate (struct map_session_data *ssd) memcpy(WBUFP(buf,6), ssd->status.name, NAME_LENGTH); - if( ssd->status.party_id > 0 ) - p = party_search(ssd->status.party_id); + if (!battle_config.display_party_name) { + if (ssd->status.party_id > 0 && ssd->status.guild_id > 0 && (g = guild_search(ssd->status.guild_id)) != NULL) + p = party_search(ssd->status.party_id); + }else{ + if (ssd->status.party_id > 0) + p = party_search(ssd->status.party_id); + } if( ssd->status.guild_id > 0 && (g = guild_search(ssd->status.guild_id)) != NULL ) { @@ -8447,7 +8443,7 @@ void clif_parse_LoadEndAck(int fd,struct map_session_data *sd) if( sd->state.bg_id ) clif_bg_hp(sd); // BattleGround System if( sd->state.changemap && map[sd->bl.m].flag.battleground ) { - clif_set01D6(sd,19); // Battleground Mode + clif_map_type(sd, MAPTYPE_BATTLEFIELD); // Battleground Mode if( map[sd->bl.m].flag.battleground == 2 ) clif_bg_updatescore_single(sd); } @@ -8462,17 +8458,17 @@ void clif_parse_LoadEndAck(int fd,struct map_session_data *sd) sd->pvp_won = 0; sd->pvp_lost = 0; } - clif_set0199(sd,1); + clif_map_property(sd, MAPPROPERTY_FREEPVPZONE); } else // set flag, if it's a duel [LuzZza] if(sd->duel_group) - clif_set0199(sd,1); + clif_map_property(sd, MAPPROPERTY_FREEPVPZONE); if (map[sd->bl.m].flag.gvg_dungeon) - clif_set0199(sd,1); //TODO: Figure out the real packet to send here. + clif_map_property(sd, MAPPROPERTY_FREEPVPZONE); //TODO: Figure out the real packet to send here. if( map_flag_gvg(sd->bl.m) ) - clif_set0199(sd,3); + clif_map_property(sd, MAPPROPERTY_AGITZONE); // info about nearby objects // must use foreachinarea (CIRCULAR_AREA interferes with foreachinrange) @@ -8876,41 +8872,6 @@ void clif_parse_GlobalMessage(int fd, struct map_session_data* sd) map_foreachinrange(npc_chat_sub, &sd->bl, AREA_SIZE, BL_NPC, text, textlen, &sd->bl); #endif - // check for special supernovice phrase - if( (sd->class_&MAPID_UPPERMASK) == MAPID_SUPER_NOVICE ) - { - unsigned int next = pc_nextbaseexp(sd); - if( next == 0 ) next = pc_thisbaseexp(sd); - if( get_percentage(sd->status.base_exp, next)% 10 == 0 ) // 0%, 10%, 20%, ... - { - switch (sd->state.snovice_call_flag) { - case 0: - if( strstr(message, msg_txt(504)) ) // "Guardian Angel, can you hear my voice? ^^;" - sd->state.snovice_call_flag++; - break; - case 1: { - char buf[256]; - sprintf(buf, msg_txt(505), sd->status.name); - if( strstr(message, buf) ) // "My name is %s, and I'm a Super Novice~" - sd->state.snovice_call_flag++; - } - break; - case 2: - if( strstr(message, msg_txt(506)) ) // "Please help me~ T.T" - sd->state.snovice_call_flag++; - break; - case 3: - if( skillnotok(MO_EXPLOSIONSPIRITS,sd) ) - break; //Do not override the noskill mapflag. [Skotlex] - clif_skill_nodamage(&sd->bl,&sd->bl,MO_EXPLOSIONSPIRITS,-1, - sc_start(&sd->bl,status_skill2sc(MO_EXPLOSIONSPIRITS),100, - 17,skill_get_time(MO_EXPLOSIONSPIRITS,1))); //Lv17-> +50 critical (noted by Poki) [Skotlex] - sd->state.snovice_call_flag = 0; - break; - } - } - } - // Chat logging type 'O' / Global Chat if( log_config.chat&1 || (log_config.chat&2 && !((agit_flag || agit2_flag) && log_config.chat&64)) ) log_chat("O", 0, sd->status.char_id, sd->status.account_id, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y, NULL, message); @@ -9215,7 +9176,7 @@ void clif_parse_WisMessage(int fd, struct map_session_data* sd) set_var(sd,output,(char *) split_data[i]); } - sprintf(output, "%s::OnWhisperGlobal", npc->name); + sprintf(output, "%s::OnWhisperGlobal", npc->exname); npc_event(sd,output,0); // Calls the NPC label return; @@ -11850,30 +11811,35 @@ void clif_parse_NoviceDoriDori(int fd, struct map_session_data *sd) } return; } -/*========================================== - * スパノビの爆裂波動 - *------------------------------------------*/ + + +/// Request to invoke the effect of super novice's guardian angel prayer (CZ_CHOPOKGI) +/// 01ed +/// Note: This packet is caused by 7 lines of any text, followed by +/// the prayer and an another line of any text. The prayer is +/// defined by lines 791~794 in data\msgstringtable.txt +/// "Dear angel, can you hear my voice?" +/// "I am" (space separated player name) "Super Novice~" +/// "Help me out~ Please~ T_T" void clif_parse_NoviceExplosionSpirits(int fd, struct map_session_data *sd) { - if(sd){ - int nextbaseexp=pc_nextbaseexp(sd); - if (battle_config.etc_log){ - if(nextbaseexp != 0) - ShowInfo("SuperNovice explosionspirits!! %d %d %d %d\n",sd->bl.id,sd->status.class_,sd->status.base_exp,(int)((double)1000*sd->status.base_exp/nextbaseexp)); - else - ShowInfo("SuperNovice explosionspirits!! %d %d %d 000\n",sd->bl.id,sd->status.class_,sd->status.base_exp); - } - if((sd->class_&MAPID_UPPERMASK) == MAPID_SUPER_NOVICE && sd->status.base_exp > 0 && nextbaseexp > 0 && (int)((double)1000*sd->status.base_exp/nextbaseexp)%100==0){ - clif_skill_nodamage(&sd->bl,&sd->bl,MO_EXPLOSIONSPIRITS,5, - sc_start(&sd->bl,status_skill2sc(MO_EXPLOSIONSPIRITS),100, - 5,skill_get_time(MO_EXPLOSIONSPIRITS,5))); + if( ( sd->class_&MAPID_UPPERMASK ) == MAPID_SUPER_NOVICE ) + { + unsigned int next = pc_nextbaseexp(sd); + + if( next ) + { + int percent = (int)( ( (float)sd->status.base_exp/(float)next )*1000. ); + + if( percent && ( percent%100 ) == 0 ) + {// 10.0%, 20.0%, ..., 90.0% + sc_start(&sd->bl, status_skill2sc(MO_EXPLOSIONSPIRITS), 100, 17, skill_get_time(MO_EXPLOSIONSPIRITS, 5)); //Lv17-> +50 critical (noted by Poki) [Skotlex] + clif_skill_nodamage(&sd->bl, &sd->bl, MO_EXPLOSIONSPIRITS, 5, 1); // prayer always shows successful Lv5 cast and disregards noskill restrictions + } } } - return; } -// random notes: -// 0x214: monster/player info ? /*========================================== * Friends List @@ -12383,11 +12349,69 @@ void clif_parse_AutoRevive(int fd, struct map_session_data *sd) pc_delitem(sd, item_position, 1, 0, 1); } -/// /check <string> -/// S 0213 <string>.24B + +/// Information about character's status values (ZC_ACK_STATUS_GM) +/// 0214 <str>.B <standardStr>.B <agi>.B <standardAgi>.B <vit>.B <standardVit>.B +/// <int>.B <standardInt>.B <dex>.B <standardDex>.B <luk>.B <standardLuk>.B +/// <attPower>.W <refiningPower>.W <max_mattPower>.W <min_mattPower>.W +/// <itemdefPower>.W <plusdefPower>.W <mdefPower>.W <plusmdefPower>.W +/// <hitSuccessValue>.W <avoidSuccessValue>.W <plusAvoidSuccessValue>.W +/// <criticalSuccessValue>.W <ASPD>.W <plusASPD>.W +void clif_check(int fd, struct map_session_data* pl_sd) +{ + WFIFOHEAD(fd,packet_len(0x214)); + WFIFOW(fd, 0) = 0x214; + WFIFOB(fd, 2) = min(pl_sd->status.str, UCHAR_MAX); + WFIFOB(fd, 3) = pc_need_status_point(pl_sd, SP_STR); + WFIFOB(fd, 4) = min(pl_sd->status.agi, UCHAR_MAX); + WFIFOB(fd, 5) = pc_need_status_point(pl_sd, SP_AGI); + WFIFOB(fd, 6) = min(pl_sd->status.vit, UCHAR_MAX); + WFIFOB(fd, 7) = pc_need_status_point(pl_sd, SP_VIT); + WFIFOB(fd, 8) = min(pl_sd->status.int_, UCHAR_MAX); + WFIFOB(fd, 9) = pc_need_status_point(pl_sd, SP_INT); + WFIFOB(fd,10) = min(pl_sd->status.dex, UCHAR_MAX); + WFIFOB(fd,11) = pc_need_status_point(pl_sd, SP_DEX); + WFIFOB(fd,12) = min(pl_sd->status.luk, UCHAR_MAX); + WFIFOB(fd,13) = pc_need_status_point(pl_sd, SP_LUK); + WFIFOW(fd,14) = pl_sd->battle_status.batk+pl_sd->battle_status.rhw.atk+pl_sd->battle_status.lhw.atk; + WFIFOW(fd,16) = pl_sd->battle_status.rhw.atk2+pl_sd->battle_status.lhw.atk2; + WFIFOW(fd,18) = pl_sd->battle_status.matk_max; + WFIFOW(fd,20) = pl_sd->battle_status.matk_min; + WFIFOW(fd,22) = pl_sd->battle_status.def; + WFIFOW(fd,24) = pl_sd->battle_status.def2; + WFIFOW(fd,26) = pl_sd->battle_status.mdef; + WFIFOW(fd,28) = pl_sd->battle_status.mdef2; + WFIFOW(fd,30) = pl_sd->battle_status.hit; + WFIFOW(fd,32) = pl_sd->battle_status.flee; + WFIFOW(fd,34) = pl_sd->battle_status.flee2/10; + WFIFOW(fd,36) = pl_sd->battle_status.cri/10; + WFIFOW(fd,38) = (2000-pl_sd->battle_status.amotion)/10; // aspd + WFIFOW(fd,40) = 0; // FIXME: What is 'plusASPD' supposed to be? + WFIFOSET(fd,packet_len(0x214)); +} + + +/// Request character's status values (CZ_REQ_STATUS_GM) +/// /check <char name> +/// 0213 <char name>.24B void clif_parse_Check(int fd, struct map_session_data *sd) { - // no info + char charname[NAME_LENGTH]; + struct map_session_data* pl_sd; + + if( pc_isGM(sd) < battle_config.gm_check_minlevel ) + { + return; + } + + safestrncpy(charname, (const char*)RFIFOP(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]), sizeof(charname)); + + if( ( pl_sd = map_nick2sd(charname) ) == NULL || pc_isGM(sd) < pc_isGM(pl_sd) ) + { + return; + } + + clif_check(fd, pl_sd); } #ifndef TXT_ONLY @@ -12826,7 +12850,7 @@ void clif_Auction_openwindow(struct map_session_data *sd) { int fd = sd->fd; - if( sd->state.storage_flag || sd->vender_id || sd->state.trading ) + if( sd->state.storage_flag || sd->vender_id || sd->state.buyingstore || sd->state.trading ) return; WFIFOHEAD(fd,12); @@ -13244,8 +13268,13 @@ void clif_parse_Adopt_reply(int fd, struct map_session_data *sd) } /*========================================== - * Convex Mirror - * S 0293 <flag>.b <x>.l <y>.l <Hours>.w <Minutes>.w <unknown>.l <monster name>.40B <unknown>.11B + * Convex Mirror (ZC_BOSS_INFO) + * S 0293 <infoType>.B <x>.L <y>.L <minHours>.W <minMinutes>.W <maxHours>.W <maxMinutes>.W <monster name>.51B + * infoType: + * 0 = No boss on this map (BOSS_INFO_NOT). + * 1 = Boss is alive (position update) (BOSS_INFO_ALIVE). + * 2 = Boss is alive (initial announce) (BOSS_INFO_ALIVE_WITHMSG). + * 3 = Boss is dead (BOSS_INFO_DEAD). *==========================================*/ void clif_bossmapinfo(int fd, struct mob_data *md, short flag) { @@ -14003,23 +14032,529 @@ void clif_showdigit(struct map_session_data* sd, unsigned char type, int value) WFIFOSET(sd->fd, packet_len(0x1b1)); } -/*========================================== - * パケットデバッグ - *------------------------------------------*/ + +/// Buying Store System +/// + +/// Opens preparation window for buying store (ZC_OPEN_BUYING_STORE) +/// 0810 <slots>.B +void clif_buyingstore_open(struct map_session_data* sd) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x810)); + WFIFOW(fd,0) = 0x810; + WFIFOB(fd,2) = sd->buyingstore.slots; + WFIFOSET(fd,packet_len(0x810)); +} + + +/// Request to create a buying store (CZ_REQ_OPEN_BUYING_STORE) +/// 0811 <packet len>.W <limit zeny>.L <result>.B <store name>.80B { <name id>.W <amount>.W <price>.L }* +/// result: +/// 0 = cancel +/// 1 = open +static void clif_parse_ReqOpenBuyingStore(int fd, struct map_session_data* sd) +{ + const unsigned int blocksize = 8; + uint8* itemlist; + char storename[MESSAGE_SIZE]; + unsigned char result; + int zenylimit; + unsigned int count, packet_len; + struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)]; + + packet_len = RFIFOW(fd,info->pos[0]); + + // TODO: Make this check global for all variable length packets. + if( packet_len < 89 ) + {// minimum packet length + ShowError("clif_parse_ReqOpenBuyingStore: Malformed packet (expected length=%u, length=%u, account_id=%d).\n", 89, packet_len, sd->bl.id); + return; + } + + zenylimit = RFIFOL(fd,info->pos[1]); + result = RFIFOL(fd,info->pos[2]); + safestrncpy(storename, (const char*)RFIFOP(fd,info->pos[3]), sizeof(storename)); + itemlist = RFIFOP(fd,info->pos[4]); + + // so that buyingstore_create knows, how many elements it has access to + packet_len-= info->pos[4]; + + if( packet_len%blocksize ) + { + ShowError("clif_parse_ReqOpenBuyingStore: Unexpected item list size %u (account_id=%d, block size=%u)\n", packet_len, sd->bl.id, blocksize); + return; + } + count = packet_len/blocksize; + + buyingstore_create(sd, zenylimit, result, storename, itemlist, count); +} + + +/// Notification, that the requested buying store could not be created (ZC_FAILED_OPEN_BUYING_STORE_TO_BUYER) +/// 0812 <result>.W <total weight>.L +/// result: +/// 1 = "Failed to open buying store." (0x6cd, MSI_BUYINGSTORE_OPEN_FAILED) +/// 2 = "Total amount of then possessed items exceeds the weight limit by <weight/10-maxweight*90%>. Please re-enter." (0x6ce, MSI_BUYINGSTORE_OVERWEIGHT) +/// 8 = "No sale (purchase) information available." (0x705) +/// other = nothing +void clif_buyingstore_open_failed(struct map_session_data* sd, unsigned short result, unsigned int weight) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x812)); + WFIFOW(fd,0) = 0x812; + WFIFOW(fd,2) = result; + WFIFOL(fd,4) = weight; + WFIFOSET(fd,packet_len(0x812)); +} + + +/// Notification, that the requested buying store was created (ZC_MYITEMLIST_BUYING_STORE) +/// 0813 <packet len>.W <account id>.L <limit zeny>.L { <price>.L <count>.W <type>.B <name id>.W }* +void clif_buyingstore_myitemlist(struct map_session_data* sd) +{ + int fd = sd->fd; + unsigned int i; + + WFIFOHEAD(fd,12+sd->buyingstore.slots*9); + WFIFOW(fd,0) = 0x813; + WFIFOW(fd,2) = 12+sd->buyingstore.slots*9; + WFIFOL(fd,4) = sd->bl.id; + WFIFOL(fd,8) = sd->buyingstore.zenylimit; + + for( i = 0; i < sd->buyingstore.slots; i++ ) + { + WFIFOL(fd,12+i*9) = sd->buyingstore.items[i].price; + WFIFOW(fd,16+i*9) = sd->buyingstore.items[i].amount; + WFIFOB(fd,18+i*9) = itemtype(itemdb_type(sd->buyingstore.items[i].nameid)); + WFIFOW(fd,19+i*9) = sd->buyingstore.items[i].nameid; + } + + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Notifies clients in area of a buying store (ZC_BUYING_STORE_ENTRY) +/// 0814 <account id>.L <store name>.80B +void clif_buyingstore_entry(struct map_session_data* sd) +{ + uint8 buf[86]; + + WBUFW(buf,0) = 0x814; + WBUFL(buf,2) = sd->bl.id; + memcpy(WBUFP(buf,6), sd->message, MESSAGE_SIZE); + + clif_send(buf, packet_len(0x814), &sd->bl, AREA_WOS); +} +void clif_buyingstore_entry_single(struct map_session_data* sd, struct map_session_data* pl_sd) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x814)); + WFIFOW(fd,0) = 0x814; + WFIFOL(fd,2) = pl_sd->bl.id; + memcpy(WFIFOP(fd,6), pl_sd->message, MESSAGE_SIZE); + WFIFOSET(fd,packet_len(0x814)); +} + + +/// Request to close own buying store (CZ_REQ_CLOSE_BUYING_STORE) +/// 0815 +static void clif_parse_ReqCloseBuyingStore(int fd, struct map_session_data* sd) +{ + buyingstore_close(sd); +} + + +/// Notifies clients in area that a buying store was closed (ZC_DISAPPEAR_BUYING_STORE_ENTRY) +/// 0816 <account id>.L +void clif_buyingstore_disappear_entry(struct map_session_data* sd) +{ + uint8 buf[6]; + + WBUFW(buf,0) = 0x816; + WBUFL(buf,2) = sd->bl.id; + + clif_send(buf, packet_len(0x816), &sd->bl, AREA_WOS); +} +void clif_buyingstore_disappear_entry_single(struct map_session_data* sd, struct map_session_data* pl_sd) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x816)); + WFIFOW(fd,0) = 0x816; + WFIFOL(fd,2) = pl_sd->bl.id; + WFIFOSET(fd,packet_len(0x816)); +} + + +/// Request to open someone else's buying store (CZ_REQ_CLICK_TO_BUYING_STORE) +/// 0817 <account id>.L +static void clif_parse_ReqClickBuyingStore(int fd, struct map_session_data* sd) +{ + int account_id; + + account_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]); + + buyingstore_open(sd, account_id); +} + + +/// Sends buying store item list (ZC_ACK_ITEMLIST_BUYING_STORE) +/// 0818 <packet len>.W <account id>.L <store id>.L <limit zeny>.L { <price>.L <amount>.W <type>.B <name id>.W }* +void clif_buyingstore_itemlist(struct map_session_data* sd, struct map_session_data* pl_sd) +{ + int fd = sd->fd; + unsigned int i; + + WFIFOHEAD(fd,16+pl_sd->buyingstore.slots*9); + WFIFOW(fd,0) = 0x818; + WFIFOW(fd,2) = 16+pl_sd->buyingstore.slots*9; + WFIFOL(fd,4) = pl_sd->bl.id; + WFIFOL(fd,8) = pl_sd->buyer_id; + WFIFOL(fd,12) = pl_sd->buyingstore.zenylimit; + + for( i = 0; i < pl_sd->buyingstore.slots; i++ ) + { + WFIFOL(fd,16+i*9) = pl_sd->buyingstore.items[i].price; + WFIFOW(fd,20+i*9) = pl_sd->buyingstore.items[i].amount; // TODO: Figure out, if no longer needed items (amount == 0) are listed on official. + WFIFOB(fd,22+i*9) = itemtype(itemdb_type(pl_sd->buyingstore.items[i].nameid)); + WFIFOW(fd,23+i*9) = pl_sd->buyingstore.items[i].nameid; + } + + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Request to sell items to a buying store (CZ_REQ_TRADE_BUYING_STORE) +/// 0819 <packet len>.W <account id>.L <store id>.L { <index>.W <name id>.W <amount>.W }* +static void clif_parse_ReqTradeBuyingStore(int fd, struct map_session_data* sd) +{ + const unsigned int blocksize = 6; + uint8* itemlist; + int account_id; + unsigned int count, packet_len, buyer_id; + struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)]; + + packet_len = RFIFOW(fd,info->pos[0]); + + if( packet_len < 12 ) + {// minimum packet length + ShowError("clif_parse_ReqTradeBuyingStore: Malformed packet (expected length=%u, length=%u, account_id=%d).\n", 12, packet_len, sd->bl.id); + return; + } + + account_id = RFIFOL(fd,info->pos[1]); + buyer_id = RFIFOL(fd,info->pos[2]); + itemlist = RFIFOP(fd,info->pos[3]); + + // so that buyingstore_trade knows, how many elements it has access to + packet_len-= info->pos[3]; + + if( packet_len%blocksize ) + { + ShowError("clif_parse_ReqTradeBuyingStore: Unexpected item list size %u (account_id=%d, buyer_id=%d, block size=%u)\n", packet_len, sd->bl.id, account_id, blocksize); + return; + } + count = packet_len/blocksize; + + buyingstore_trade(sd, account_id, buyer_id, itemlist, count); +} + + +/// Notifies the buyer, that the buying store has been closed due to a post-trade condition (ZC_FAILED_TRADE_BUYING_STORE_TO_BUYER) +/// 081a <result>.W +/// result: +/// 3 = "All items within the buy limit were purchased." (0x6cf, MSI_BUYINGSTORE_TRADE_OVERLIMITZENY) +/// 4 = "All items were purchased." (0x6d0, MSI_BUYINGSTORE_TRADE_BUYCOMPLETE) +/// other = nothing +void clif_buyingstore_trade_failed_buyer(struct map_session_data* sd, short result) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x81a)); + WFIFOW(fd,0) = 0x81a; + WFIFOW(fd,2) = result; + WFIFOSET(fd,packet_len(0x81a)); +} + + +/// Updates the zeny limit and an item in the buying store item list (ZC_UPDATE_ITEM_FROM_BUYING_STORE) +/// 081b <name id>.W <amount>.W <limit zeny>.L +void clif_buyingstore_update_item(struct map_session_data* sd, unsigned short nameid, unsigned short amount) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x81b)); + WFIFOW(fd,0) = 0x81b; + WFIFOW(fd,2) = nameid; + WFIFOW(fd,4) = amount; // amount of nameid received + WFIFOW(fd,6) = sd->buyingstore.zenylimit; + WFIFOSET(fd,packet_len(0x81b)); +} + + +/// Deletes item from inventory, that was sold to a buying store (ZC_ITEM_DELETE_BUYING_STORE) +/// 081c <index>.W <amount>.W <price>.L +/// message: +/// "%s (%d) were sold at %dz." (0x6d2, MSI_BUYINGSTORE_TRADE_SELLCOMPLETE) +/// +/// @note This function has to be called _instead_ of clif_delitem/clif_dropitem. +void clif_buyingstore_delete_item(struct map_session_data* sd, short index, unsigned short amount, int price) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x81c)); + WFIFOW(fd,0) = 0x81c; + WFIFOW(fd,2) = index+2; + WFIFOW(fd,4) = amount; + WFIFOL(fd,6) = price; // price per item, client calculates total Zeny by itself + WFIFOSET(fd,packet_len(0x81c)); +} + + +/// Notifies the seller, that a buying store trade failed (ZC_FAILED_TRADE_BUYING_STORE_TO_SELLER) +/// 0824 <result>.W <name id>.W +/// result: +/// 5 = "The deal has failed." (0x39, MSI_DEAL_FAIL) +/// 6 = "The trade failed, because the entered amount of item %s is higher, than the buyer is willing to buy." (0x6d3, MSI_BUYINGSTORE_TRADE_OVERCOUNT) +/// 7 = "The trade failed, because the buyer is lacking required balance." (0x6d1, MSI_BUYINGSTORE_TRADE_LACKBUYERZENY) +/// other = nothing +void clif_buyingstore_trade_failed_seller(struct map_session_data* sd, short result, unsigned short nameid) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x824)); + WFIFOW(fd,0) = 0x824; + WFIFOW(fd,2) = result; + WFIFOW(fd,4) = nameid; + WFIFOSET(fd,packet_len(0x824)); +} + + +/// Search Store Info System +/// + +/// Request to search for stores (CZ_SEARCH_STORE_INFO) +/// 0835 <packet len>.W <type>.B <max price>.L <min price>.L <name id count>.B <card count>.B { <name id>.W }* { <card>.W }* +/// type: +/// 0 = Vending +/// 1 = Buying Store +/// +/// @note The client determines the item ids by specifying a name and optionally, +/// amount of card slots. If the client does not know about the item it +/// cannot be searched. +static void clif_parse_SearchStoreInfo(int fd, struct map_session_data* sd) +{ + const unsigned int blocksize = 2; + const uint8* itemlist; + const uint8* cardlist; + unsigned char type; + unsigned int min_price, max_price, packet_len, count, item_count, card_count; + struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)]; + + packet_len = RFIFOW(fd,info->pos[0]); + + if( packet_len < 15 ) + {// minimum packet length + ShowError("clif_parse_SearchStoreInfo: Malformed packet (expected length=%u, length=%u, account_id=%d).\n", 15, packet_len, sd->bl.id); + return; + } + + type = RFIFOB(fd,info->pos[1]); + max_price = RFIFOL(fd,info->pos[2]); + min_price = RFIFOL(fd,info->pos[3]); + item_count = RFIFOB(fd,info->pos[4]); + card_count = RFIFOB(fd,info->pos[5]); + itemlist = RFIFOP(fd,info->pos[6]); + cardlist = RFIFOP(fd,info->pos[6]+blocksize*item_count); + + // check, if there is enough data for the claimed count of items + packet_len-= info->pos[6]; + + if( packet_len%blocksize ) + { + ShowError("clif_parse_SearchStoreInfo: Unexpected item list size %u (account_id=%d, block size=%u)\n", packet_len, sd->bl.id, blocksize); + return; + } + count = packet_len/blocksize; + + if( count < item_count+card_count ) + { + ShowError("clif_parse_SearchStoreInfo: Malformed packet (expected count=%u, count=%u, account_id=%d).\n", item_count+card_count, count, sd->bl.id); + return; + } + + searchstore_query(sd, type, min_price, max_price, (const unsigned short*)itemlist, item_count, (const unsigned short*)cardlist, card_count); +} + + +/// Results for a store search request (ZC_SEARCH_STORE_INFO_ACK) +/// 0836 <packet len>.W <is first page>.B <is next page>.B <remaining uses>.B { <store id>.L <account id>.L <shop name>.80B <nameid>.W <item type>.B <price>.L <amount>.W <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W }* +/// is first page: +/// 0 = appends to existing results +/// 1 = clears previous results before displaying this result set +/// is next page: +/// 0 = no "next" label +/// 1 = "next" label to retrieve more results +void clif_search_store_info_ack(struct map_session_data* sd) +{ + const unsigned int blocksize = MESSAGE_SIZE+26; + int fd = sd->fd; + unsigned int i, start, end; + + start = sd->searchstore.pages*SEARCHSTORE_RESULTS_PER_PAGE; + end = min(sd->searchstore.count, start+SEARCHSTORE_RESULTS_PER_PAGE); + + WFIFOHEAD(fd,7+(end-start)*blocksize); + WFIFOW(fd,0) = 0x836; + WFIFOW(fd,2) = 7+(end-start)*blocksize; + WFIFOB(fd,4) = !sd->searchstore.pages; + WFIFOB(fd,5) = searchstore_querynext(sd); + WFIFOB(fd,6) = (unsigned char)min(sd->searchstore.uses, UCHAR_MAX); + + for( i = start; i < end; i++ ) + { + struct s_search_store_info_item* ssitem = &sd->searchstore.items[i]; + struct item it; + + WFIFOL(fd,i*blocksize+ 7) = ssitem->store_id; + WFIFOL(fd,i*blocksize+11) = ssitem->account_id; + memcpy(WFIFOP(fd,i*blocksize+15), ssitem->store_name, MESSAGE_SIZE); + WFIFOW(fd,i*blocksize+15+MESSAGE_SIZE) = ssitem->nameid; + WFIFOB(fd,i*blocksize+17+MESSAGE_SIZE) = itemtype(itemdb_type(ssitem->nameid)); + WFIFOL(fd,i*blocksize+18+MESSAGE_SIZE) = ssitem->price; + WFIFOW(fd,i*blocksize+22+MESSAGE_SIZE) = ssitem->amount; + WFIFOB(fd,i*blocksize+24+MESSAGE_SIZE) = ssitem->refine; + + // make-up an item for clif_addcards + memset(&it, 0, sizeof(it)); + memcpy(&it.card, &ssitem->card, sizeof(it.card)); + it.nameid = ssitem->nameid; + it.amount = ssitem->amount; + + clif_addcards(WFIFOP(fd,i*blocksize+25+MESSAGE_SIZE), &it); + } + + WFIFOSET(fd,WFIFOW(fd,2)); +} + + +/// Notification of failure when searching for stores (ZC_SEARCH_STORE_INFO_FAILED) +/// 0837 <reason>.B +/// reason: +/// 0 = "No matching stores were found." (0x70b) +/// 1 = "There are too many results. Please enter more detailed search term." (0x6f8) +/// 2 = "You cannot search anymore." (0x706) +/// 3 = "You cannot search yet." (0x708) +/// 4 = "No sale (purchase) information available." (0x705) +void clif_search_store_info_failed(struct map_session_data* sd, unsigned char reason) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x837)); + WFIFOW(fd,0) = 0x837; + WFIFOB(fd,2) = reason; + WFIFOSET(fd,packet_len(0x837)); +} + + +/// Request to display next page of results (CZ_SEARCH_STORE_INFO_NEXT_PAGE) +/// 0838 +static void clif_parse_SearchStoreInfoNextPage(int fd, struct map_session_data* sd) +{ + searchstore_next(sd); +} + + +/// Opens the search store window (ZC_OPEN_SEARCH_STORE_INFO) +/// 083a <type>.W <remaining uses>.B +/// type: +/// 0 = Search Stores +/// 1 = Search Stores (Cash), asks for confirmation, when clicking a store +void clif_open_search_store_info(struct map_session_data* sd) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x83a)); + WFIFOW(fd,0) = 0x83a; + WFIFOW(fd,2) = sd->searchstore.effect; +#if PACKETVER > 20100701 + WFIFOB(fd,4) = (unsigned char)min(sd->searchstore.uses, UCHAR_MAX); +#endif + WFIFOSET(fd,packet_len(0x83a)); +} + + +/// Request to close the store search window (CZ_CLOSE_SEARCH_STORE_INFO) +/// 083b +static void clif_parse_CloseSearchStoreInfo(int fd, struct map_session_data* sd) +{ + searchstore_close(sd); +} + + +/// Request to invoke catalog effect on a store from search results (CZ_SSILIST_ITEM_CLICK) +/// 083c <account id>.L <store id>.L <nameid>.W +static void clif_parse_SearchStoreInfoListItemClick(int fd, struct map_session_data* sd) +{ + unsigned short nameid; + int account_id, store_id; + struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)]; + + account_id = RFIFOL(fd,info->pos[0]); + store_id = RFIFOL(fd,info->pos[1]); + nameid = RFIFOW(fd,info->pos[2]); + + searchstore_click(sd, account_id, store_id, nameid); +} + + +/// Notification of the store position on current map (ZC_SSILIST_ITEM_CLICK_ACK) +/// 083d <xPos>.W <yPos>.W +void clif_search_store_info_click_ack(struct map_session_data* sd, short x, short y) +{ + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0x83d)); + WFIFOW(fd,0) = 0x83d; + WFIFOW(fd,2) = x; + WFIFOW(fd,4) = y; + WFIFOSET(fd,packet_len(0x83d)); +} + + +/// Parse function for packet debugging void clif_parse_debug(int fd,struct map_session_data *sd) { - int i, cmd, len; + int cmd, packet_len; + // clif_parse ensures, that there is at least 2 bytes of data cmd = RFIFOW(fd,0); - len = sd?packet_db[sd->packet_ver][cmd].len:RFIFOREST(fd); //With no session, just read the remaining in the buffer. - ShowDebug("packet debug 0x%4X\n",cmd); - ShowMessage("---- 00-01-02-03-04-05-06-07-08-09-0A-0B-0C-0D-0E-0F"); - for(i=0;i<len;i++){ - if((i&15)==0) - ShowMessage("\n%04X ",i); - ShowMessage("%02X ",RFIFOB(fd,i)); + + if( sd ) + { + packet_len = packet_db[sd->packet_ver][cmd].len; + + if( packet_len == 0 ) + {// unknown + packet_len = RFIFOREST(fd); + } + else if( packet_len == -1 ) + {// variable length + packet_len = RFIFOW(fd,2); // clif_parse ensures, that this amount of data is already received + } + ShowDebug("Packet debug of 0x%04X (length %d), %s session #%d, %d/%d (AID/CID)\n", cmd, packet_len, sd->state.active ? "authed" : "unauthed", fd, sd->status.account_id, sd->status.char_id); + } + else + { + packet_len = RFIFOREST(fd); + ShowDebug("Packet debug of 0x%04X (length %d), session #%d\n", cmd, packet_len, fd); } - ShowMessage("\n"); + + ShowDump(RFIFOP(fd,0), packet_len); } /*========================================== @@ -14089,6 +14624,9 @@ int clif_parse(int fd) WFIFOW(fd,0) = 0x6a; WFIFOB(fd,2) = 3; // Rejected from Server WFIFOSET(fd,packet_len(0x6a)); +#ifdef DUMP_INVALID_PACKET + ShowDump(RFIFOP(fd,0), RFIFOREST(fd)); +#endif RFIFOSKIP(fd, RFIFOREST(fd)); set_eof(fd); return 0; @@ -14098,6 +14636,9 @@ int clif_parse(int fd) // filter out invalid / unsupported packets if (cmd > MAX_PACKET_DB || packet_db[packet_ver][cmd].len == 0) { ShowWarning("clif_parse: Received unsupported packet (packet 0x%04x, %d bytes received), disconnecting session #%d.\n", cmd, RFIFOREST(fd), fd); +#ifdef DUMP_INVALID_PACKET + ShowDump(RFIFOP(fd,0), RFIFOREST(fd)); +#endif set_eof(fd); return 0; } @@ -14111,6 +14652,9 @@ int clif_parse(int fd) packet_len = RFIFOW(fd,2); if (packet_len < 4 || packet_len > 32768) { ShowWarning("clif_parse: Received packet 0x%04x specifies invalid packet_len (%d), disconnecting session #%d.\n", cmd, packet_len, fd); +#ifdef DUMP_INVALID_PACKET + ShowDump(RFIFOP(fd,0), RFIFOREST(fd)); +#endif set_eof(fd); return 0; } @@ -14134,58 +14678,45 @@ int clif_parse(int fd) else packet_db[packet_ver][cmd].func(fd, sd); } -#if DUMP_UNKNOWN_PACKET - else if (battle_config.error_log) +#ifdef DUMP_UNKNOWN_PACKET + else { - int i; - FILE *fp; - char packet_txt[256] = "save/packet.txt"; - time_t now; - dump = 1; + const char* packet_txt = "save/packet.txt"; + FILE* fp; - if ((fp = fopen(packet_txt, "a")) == NULL) { - ShowError("clif.c: can't write [%s] !!! data is lost !!!\n", packet_txt); - return 1; - } else { - time(&now); - if (sd && sd->state.active) { - fprintf(fp, "%sPlayer with account ID %d (character ID %d, player name %s) sent wrong packet:\n", - asctime(localtime(&now)), sd->status.account_id, sd->status.char_id, sd->status.name); - } else if (sd) // not authentified! (refused by char-server or disconnect before to be authentified) - fprintf(fp, "%sPlayer with account ID %d sent wrong packet:\n", asctime(localtime(&now)), sd->bl.id); - - fprintf(fp, "\t---- 00-01-02-03-04-05-06-07-08-09-0A-0B-0C-0D-0E-0F"); - for(i = 0; i < packet_len; i++) { - if ((i & 15) == 0) - fprintf(fp, "\n\t%04X ", i); - fprintf(fp, "%02X ", RFIFOB(fd,i)); + if((fp = fopen(packet_txt, "a"))!=NULL) + { + if( sd ) + { + fprintf(fp, "Unknown packet 0x%04X (length %d), %s session #%d, %d/%d (AID/CID)\n", cmd, packet_len, sd->state.active ? "authed" : "unauthed", fd, sd->status.account_id, sd->status.char_id); } - fprintf(fp, "\n\n"); + else + { + fprintf(fp, "Unknown packet 0x%04X (length %d), session #%d\n", cmd, packet_len, fd); + } + + WriteDump(fp, RFIFOP(fd,0), packet_len); + fprintf(fp, "\n"); fclose(fp); } - } -#endif + else + { + ShowError("Failed to write '%s'.\n", packet_txt); - /* TODO: use utils.c :: dump() - if (dump) { - int i; - ShowDebug("\nclif_parse: session #%d, packet 0x%04x, length %d, version %d\n", fd, cmd, packet_len, packet_ver); - ShowMessage("---- 00-01-02-03-04-05-06-07-08-09-0A-0B-0C-0D-0E-0F"); - for(i = 0; i < packet_len; i++) { - if ((i & 15) == 0) - ShowMessage("\n%04X ",i); - ShowMessage("%02X ", RFIFOB(fd,i)); - } - ShowMessage("\n"); - if (sd && sd->state.active) { - if (sd->status.name != NULL) - ShowMessage("\nAccount ID %d, character ID %d, player name %s.\n", - sd->status.account_id, sd->status.char_id, sd->status.name); + // Dump on console instead + if( sd ) + { + ShowDebug("Unknown packet 0x%04X (length %d), %s session #%d, %d/%d (AID/CID)\n", cmd, packet_len, sd->state.active ? "authed" : "unauthed", fd, sd->status.account_id, sd->status.char_id); + } else - ShowMessage("\nAccount ID %d.\n", sd->bl.id); - } else if (sd) // not authentified! (refused by char-server or disconnect before to be authentified) - ShowMessage("\nAccount ID %d.\n", sd->bl.id); - }*/ + { + ShowDebug("Unknown packet 0x%04X (length %d), session #%d\n", cmd, packet_len, fd); + } + + ShowDump(RFIFOP(fd,0), packet_len); + } + } +#endif RFIFOSKIP(fd, packet_len); @@ -14405,9 +14936,9 @@ static int packetdb_readdb(void) #else // for Party booking ( PACKETVER >= 20091229 ) -1, -1, 18, 4, 8, 6, 2, 4, 14, 50, 18, 6, 2, 3, 14, 20, #endif - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3, -1, 8, -1, 86, 2, 6, 6, -1, -1, 4, 10, 10, 0, 0, 0, + 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, -1, -1, 3, 2, 66, 5, 2, 12, 6, 0, 0, }; struct { void (*func)(int, struct map_session_data *); @@ -14598,6 +15129,16 @@ static int packetdb_readdb(void) {clif_parse_PartyBookingDeleteReq,"bookingdelreq"}, #endif {clif_parse_PVPInfo,"pvpinfo"}, + // Buying Store + {clif_parse_ReqOpenBuyingStore,"reqopenbuyingstore"}, + {clif_parse_ReqCloseBuyingStore,"reqclosebuyingstore"}, + {clif_parse_ReqClickBuyingStore,"reqclickbuyingstore"}, + {clif_parse_ReqTradeBuyingStore,"reqtradebuyingstore"}, + // Store Search + {clif_parse_SearchStoreInfo,"searchstoreinfo"}, + {clif_parse_SearchStoreInfoNextPage,"searchstoreinfonextpage"}, + {clif_parse_CloseSearchStoreInfo,"closesearchstoreinfo"}, + {clif_parse_SearchStoreInfoListItemClick,"searchstoreinfolistitemclick"}, {NULL,NULL} }; diff --git a/src/map/clif.h b/src/map/clif.h index 8e9f2be7a..85293405d 100644 --- a/src/map/clif.h +++ b/src/map/clif.h @@ -173,6 +173,43 @@ typedef enum clr_type CLR_TELEPORT, } clr_type; +enum map_property +{// clif_map_property + MAPPROPERTY_NOTHING = 0, + MAPPROPERTY_FREEPVPZONE = 1, + MAPPROPERTY_EVENTPVPZONE = 2, + MAPPROPERTY_AGITZONE = 3, + MAPPROPERTY_PKSERVERZONE = 4, // message "You are in a PK area. Please beware of sudden attacks." in color 0x9B9BFF (light red) + MAPPROPERTY_PVPSERVERZONE = 5, + MAPPROPERTY_DENYSKILLZONE = 6, +}; + +enum map_type +{// clif_map_type + MAPTYPE_VILLAGE = 0, + MAPTYPE_VILLAGE_IN = 1, + MAPTYPE_FIELD = 2, + MAPTYPE_DUNGEON = 3, + MAPTYPE_ARENA = 4, + MAPTYPE_PENALTY_FREEPKZONE = 5, + MAPTYPE_NOPENALTY_FREEPKZONE = 6, + MAPTYPE_EVENT_GUILDWAR = 7, + MAPTYPE_AGIT = 8, + MAPTYPE_DUNGEON2 = 9, + MAPTYPE_DUNGEON3 = 10, + MAPTYPE_PKSERVER = 11, + MAPTYPE_PVPSERVER = 12, + MAPTYPE_DENYSKILL = 13, + MAPTYPE_TURBOTRACK = 14, + MAPTYPE_JAIL = 15, + MAPTYPE_MONSTERTRACK = 16, + MAPTYPE_PORINGBATTLE = 17, + MAPTYPE_AGIT_SIEGEV15 = 18, + MAPTYPE_BATTLEFIELD = 19, + MAPTYPE_PVP_TOURNAMENT = 20, + MAPTYPE_UNUSED = 29, +}; + int clif_setip(const char* ip); void clif_setbindip(const char* ip); void clif_setport(uint16 port); @@ -284,7 +321,6 @@ int clif_class_change(struct block_list *bl,int class_,int type); #define clif_mob_class_change(md, class_) clif_class_change(&md->bl, class_, 1) int clif_mob_equip(struct mob_data *md,int nameid); // [Valaris] -int clif_skillinfo(struct map_session_data *sd,int skillid,int type,int range); int clif_skillinfoblock(struct map_session_data *sd); int clif_skillup(struct map_session_data *sd,int skill_num); int clif_addskill(struct map_session_data *sd, int skill); @@ -438,9 +474,9 @@ void clif_MainChatMessage(const char* message); //luzza int clif_broadcast2(struct block_list *bl, const char* mes, int len, unsigned long fontColor, short fontType, short fontSize, short fontAlign, short fontY, enum send_target target); int clif_heal(int fd,int type,int val); int clif_resurrection(struct block_list *bl,int type); -void clif_set0199(struct map_session_data* sd, int mode); +void clif_map_property(struct map_session_data* sd, enum map_property property); int clif_pvpset(struct map_session_data *sd, int pvprank, int pvpnum,int type); -int clif_send0199(int map,int type); +void clif_map_property_mapall(int map, enum map_property property); void clif_refine(int fd, int fail, int index, int val); void clif_upgrademessage(int fd, int result, int item_id); @@ -569,4 +605,24 @@ void clif_PartyBookingInsertNotify(struct map_session_data* sd, struct party_boo void clif_showdigit(struct map_session_data* sd, unsigned char type, int value); +/// Buying Store System +void clif_buyingstore_open(struct map_session_data* sd); +void clif_buyingstore_open_failed(struct map_session_data* sd, unsigned short result, unsigned int weight); +void clif_buyingstore_myitemlist(struct map_session_data* sd); +void clif_buyingstore_entry(struct map_session_data* sd); +void clif_buyingstore_entry_single(struct map_session_data* sd, struct map_session_data* pl_sd); +void clif_buyingstore_disappear_entry(struct map_session_data* sd); +void clif_buyingstore_disappear_entry_single(struct map_session_data* sd, struct map_session_data* pl_sd); +void clif_buyingstore_itemlist(struct map_session_data* sd, struct map_session_data* pl_sd); +void clif_buyingstore_trade_failed_buyer(struct map_session_data* sd, short result); +void clif_buyingstore_update_item(struct map_session_data* sd, unsigned short nameid, unsigned short amount); +void clif_buyingstore_delete_item(struct map_session_data* sd, short index, unsigned short amount, int price); +void clif_buyingstore_trade_failed_seller(struct map_session_data* sd, short result, unsigned short nameid); + +/// Search Store System +void clif_search_store_info_ack(struct map_session_data* sd); +void clif_search_store_info_failed(struct map_session_data* sd, unsigned char reason); +void clif_open_search_store_info(struct map_session_data* sd); +void clif_search_store_info_click_ack(struct map_session_data* sd, short x, short y); + #endif /* _CLIF_H_ */ diff --git a/src/map/itemdb.c b/src/map/itemdb.c index bf801fefd..92fe757c8 100644 --- a/src/map/itemdb.c +++ b/src/map/itemdb.c @@ -703,6 +703,32 @@ static bool itemdb_read_stack(char* fields[], int columns, int current) return true; } +/// Reads items allowed to be sold in buying stores +static bool itemdb_read_buyingstore(char* fields[], int columns, int current) +{// <nameid> + int nameid; + struct item_data* id; + + nameid = atoi(fields[0]); + + if( ( id = itemdb_exists(nameid) ) == NULL ) + { + ShowWarning("itemdb_read_buyingstore: Invalid item id %d.\n", nameid); + return false; + } + + if( !itemdb_isstackable2(id) ) + { + ShowWarning("itemdb_read_buyingstore: Non-stackable item id %d cannot be enabled for buying store.\n", nameid); + return false; + } + + id->flag.buyingstore = true; + + return true; +} + + /*====================================== * Applies gender restrictions according to settings. [Skotlex] *======================================*/ @@ -1019,6 +1045,7 @@ static void itemdb_read(void) sv_readdb(db_path, "item_noequip.txt", ',', 2, 2, -1, &itemdb_read_noequip); sv_readdb(db_path, "item_trade.txt", ',', 3, 3, -1, &itemdb_read_itemtrade); sv_readdb(db_path, "item_delay.txt", ',', 2, 2, MAX_ITEMDELAYS, &itemdb_read_itemdelay); + sv_readdb(db_path, "item_buyingstore.txt", ',', 1, 1, -1, &itemdb_read_buyingstore); sv_readdb(db_path, "item_stack.txt", ',', 3, 3, -1, &itemdb_read_stack); } diff --git a/src/map/itemdb.h b/src/map/itemdb.h index 1b8917277..b24944fb5 100644 --- a/src/map/itemdb.h +++ b/src/map/itemdb.h @@ -76,6 +76,7 @@ struct item_data { unsigned delay_consume : 1; // Signifies items that are not consumed immediately upon double-click [Skotlex] unsigned trade_restriction : 7; //Item restrictions mask [Skotlex] unsigned autoequip: 1; + unsigned buyingstore : 1; } flag; struct {// item stacking limitation diff --git a/src/map/log.c b/src/map/log.c index bf6133684..72cfe46d3 100644 --- a/src/map/log.c +++ b/src/map/log.c @@ -492,31 +492,31 @@ int log_config_read(char *cfgName) else if(strcmpi(w1, "log_branch_file") == 0) { strcpy(log_config.log_branch, w2); if(log_config.branch > 0 && !log_config.sql_logs) - ShowNotice("Logging Dead Branch Usage to file `%s`.txt\n", w2); + ShowNotice("Logging Dead Branch Usage to file `%s`\n", w2); } else if(strcmpi(w1, "log_pick_file") == 0) { strcpy(log_config.log_pick, w2); if(log_config.filter > 0 && !log_config.sql_logs) - ShowNotice("Logging Item Picks to file `%s`.txt\n", w2); + ShowNotice("Logging Item Picks to file `%s`\n", w2); } else if(strcmpi(w1, "log_zeny_file") == 0) { strcpy(log_config.log_zeny, w2); if(log_config.zeny > 0 && !log_config.sql_logs) - ShowNotice("Logging Zeny to file `%s`.txt\n", w2); + ShowNotice("Logging Zeny to file `%s`\n", w2); } else if(strcmpi(w1, "log_mvpdrop_file") == 0) { strcpy(log_config.log_mvpdrop, w2); if(log_config.mvpdrop > 0 && !log_config.sql_logs) - ShowNotice("Logging MVP Drops to file `%s`.txt\n", w2); + ShowNotice("Logging MVP Drops to file `%s`\n", w2); } else if(strcmpi(w1, "log_gm_file") == 0) { strcpy(log_config.log_gm, w2); if(log_config.gm > 0 && !log_config.sql_logs) - ShowNotice("Logging GM Level %d Commands to file `%s`.txt\n", log_config.gm, w2); + ShowNotice("Logging GM Level %d Commands to file `%s`\n", log_config.gm, w2); } else if(strcmpi(w1, "log_npc_file") == 0) { strcpy(log_config.log_npc, w2); if(log_config.npc > 0 && !log_config.sql_logs) - ShowNotice("Logging NPC 'logmes' to file `%s`.txt\n", w2); + ShowNotice("Logging NPC 'logmes' to file `%s`\n", w2); } else if(strcmpi(w1, "log_chat_file") == 0) { strcpy(log_config.log_chat, w2); if(log_config.chat > 0 && !log_config.sql_logs) - ShowNotice("Logging CHAT to file `%s`.txt\n", w2); + ShowNotice("Logging CHAT to file `%s`\n", w2); //support the import command, just like any other config } else if(strcmpi(w1,"import") == 0) { log_config_read(w2); diff --git a/src/map/log.h b/src/map/log.h index 9dfc0c0ea..f88236489 100644 --- a/src/map/log.h +++ b/src/map/log.h @@ -36,9 +36,10 @@ typedef enum log_what { LOG_USED_ITEMS = 0x0100, // used by player LOG_MVP_PRIZE = 0x0200, LOG_COMMAND_ITEMS = 0x0400, // created/deleted through @/# commands - LOG_STORAGE_ITEMS = 0x0800, // placed/retrieved from storage - LOG_GSTORAGE_ITEMS = 0x1000, // placed/retrieved from guild storage - LOG_MAILS = 0x2000 // mail system transactions + LOG_STORAGE_ITEMS = 0x0800, // placed/retrieved from storage + LOG_GSTORAGE_ITEMS = 0x1000, // placed/retrieved from guild storage + LOG_MAILS = 0x2000, // mail system transactions + LOG_BUYING_STORE = 0x4000, // buying store transactions } log_what; extern struct Log_Config { diff --git a/src/map/mail.c b/src/map/mail.c index f0869185a..c866bfb60 100644 --- a/src/map/mail.c +++ b/src/map/mail.c @@ -162,7 +162,7 @@ int mail_openmail(struct map_session_data *sd) { nullpo_ret(sd); - if( sd->state.storage_flag || sd->vender_id || sd->state.trading ) + if( sd->state.storage_flag || sd->vender_id || sd->state.buyingstore || sd->state.trading ) return 0; clif_Mail_window(sd->fd, 0); diff --git a/src/map/map.c b/src/map/map.c index e3cada963..2cd146ee4 100644 --- a/src/map/map.c +++ b/src/map/map.c @@ -2781,6 +2781,7 @@ void map_flags_init(void) map[i].nocommand = 0; // nocommand mapflag level map[i].bexp = 100; // per map base exp multiplicator map[i].jexp = 100; // per map job exp multiplicator + memset(map[i].drop_list, 0, sizeof(map[i].drop_list)); // pvp nightmare drop list // adjustments if( battle_config.pk_mode ) diff --git a/src/map/npc.c b/src/map/npc.c index 1bf5bd312..bc2018459 100644 --- a/src/map/npc.c +++ b/src/map/npc.c @@ -2263,6 +2263,7 @@ const char* npc_parse_duplicate(char* w1, char* w2, char* w3, char* w4, const ch char srcname[128]; int i; const char* end; + size_t length; int src_id; int type; @@ -2270,12 +2271,16 @@ const char* npc_parse_duplicate(char* w1, char* w2, char* w3, char* w4, const ch struct npc_data* dnd; end = strchr(start,'\n'); + length = strlen(w2); + // get the npc being duplicated - if( sscanf(w2,"duplicate(%127[^)])",srcname) != 1 ) - { + if( w2[length-1] != ')' || length <= 11 || length-11 >= sizeof(srcname) ) + {// does not match 'duplicate(%127s)', name is empty or too long ShowError("npc_parse_script: bad duplicate name in file '%s', line '%d' : %s\n", filepath, strline(buffer,start-buffer), w2); return end;// next line, try to continue } + safestrncpy(srcname, w2+10, length-10); + dnd = npc_name2id(srcname); if( dnd == NULL) { ShowError("npc_parse_script: original npc not found for duplicate in file '%s', line '%d' : %s\n", filepath, strline(buffer,start-buffer), srcname); diff --git a/src/map/party.c b/src/map/party.c index 9c55a05f2..21867c95a 100644 --- a/src/map/party.c +++ b/src/map/party.c @@ -309,8 +309,8 @@ int party_invite(struct map_session_data *sd,struct map_session_data *tsd) return 0; } - if ( (pc_isGM(sd) > battle_config.lowest_gm_level && pc_isGM(tsd) < battle_config.lowest_gm_level && !battle_config.gm_can_party && pc_isGM(sd) < battle_config.gm_cant_party_min_lv) - || ( pc_isGM(sd) < battle_config.lowest_gm_level && pc_isGM(tsd) > battle_config.lowest_gm_level && !battle_config.gm_can_party && pc_isGM(tsd) < battle_config.gm_cant_party_min_lv) ) + if ( (pc_isGM(sd) >= battle_config.lowest_gm_level && pc_isGM(tsd) < battle_config.lowest_gm_level && !battle_config.gm_can_party && pc_isGM(sd) < battle_config.gm_cant_party_min_lv) + || ( pc_isGM(sd) < battle_config.lowest_gm_level && pc_isGM(tsd) >= battle_config.lowest_gm_level && !battle_config.gm_can_party && pc_isGM(tsd) < battle_config.gm_cant_party_min_lv) ) { //GMs can't invite non GMs to the party if not above the invite trust level //Likewise, as long as gm_can_party is off, players can't invite GMs. diff --git a/src/map/pc.c b/src/map/pc.c index 7525ff63c..ffbd2d3cf 100644 --- a/src/map/pc.c +++ b/src/map/pc.c @@ -893,6 +893,7 @@ bool pc_authok(struct map_session_data *sd, int login_id2, time_t expiration_tim sd->state.showdelay = 1; pc_setinventorydata(sd); + pc_setequipindex(sd); status_change_init(&sd->bl); if ((battle_config.atc_gmonly == 0 || pc_isGM(sd)) && (pc_isGM(sd) >= get_atcommand_level(atcommand_hide))) @@ -1540,7 +1541,7 @@ static int pc_bonus_addeff(struct s_addeffect* effect, int max, enum sc_type id, if (!(flag&(ATF_TARGET|ATF_SELF))) flag|=ATF_TARGET; //Default target: enemy. if (!(flag&(ATF_WEAPON|ATF_MAGIC|ATF_MISC))) - flag|=ATF_WEAPON; //Defatul type: weapon. + flag|=ATF_WEAPON; //Default type: weapon. for (i = 0; i < max && effect[i].flag; i++) { if (effect[i].id == id && effect[i].flag == flag) @@ -2934,7 +2935,7 @@ int pc_bonus3(struct map_session_data *sd,int type,int type2,int type3,int val) break; } if( sd->state.lr_flag != 2 ) - pc_bonus_addeff_onskill(sd->addeff3, ARRAYLENGTH(sd->addeff3), (sc_type)type3, val, type2, 2); + pc_bonus_addeff_onskill(sd->addeff3, ARRAYLENGTH(sd->addeff3), (sc_type)type3, val, type2, ATF_TARGET); break; case SP_ADDELE: @@ -5047,7 +5048,7 @@ int pc_gainexp(struct map_session_data *sd, struct block_list *src, unsigned int if(sd->state.showexp) { char output[256]; sprintf(output, - "Experience Gained Base:%u (%.2f%%) Job:%u (%.2f%%)", base_exp, nextbp*(float)100, job_exp, nextjp*(float)100); + "Experience Gained Base:%u (%.2f%%) Job:%u (%.2f%%)",base_exp,nextbp*(float)100,job_exp,nextjp*(float)100); clif_disp_onlyself(sd,output,strlen(output)); } @@ -7875,7 +7876,7 @@ int duel_create(struct map_session_data* sd, const unsigned int maxpl) strcpy(output, msg_txt(372)); // " -- Duel has been created (@invite/@leave) --" clif_disp_onlyself(sd, output, strlen(output)); - clif_set0199(sd, 1); + clif_map_property(sd, MAPPROPERTY_FREEPVPZONE); //clif_misceffect2(&sd->bl, 159); return i; } @@ -7922,7 +7923,7 @@ int duel_leave(const unsigned int did, struct map_session_data* sd) sd->duel_group = 0; duel_savetime(sd); - clif_set0199(sd, 0); + clif_map_property(sd, MAPPROPERTY_NOTHING); return 0; } @@ -7939,7 +7940,7 @@ int duel_accept(const unsigned int did, struct map_session_data* sd) sprintf(output, msg_txt(376), sd->status.name); clif_disp_message(&sd->bl, output, strlen(output), DUEL_WOS); - clif_set0199(sd, 1); + clif_map_property(sd, MAPPROPERTY_FREEPVPZONE); //clif_misceffect2(&sd->bl, 159); return 0; } diff --git a/src/map/pc.h b/src/map/pc.h index c48defbf5..b9ccf3e88 100644 --- a/src/map/pc.h +++ b/src/map/pc.h @@ -7,10 +7,12 @@ #include "../common/mmo.h" // JOB_*, MAX_FAME_LIST, struct fame_list, struct mmo_charstatus #include "../common/timer.h" // INVALID_TIMER #include "battle.h" // battle_config +#include "buyingstore.h" // struct s_buyingstore #include "itemdb.h" // MAX_ITEMGROUP #include "map.h" // RC_MAX #include "pc.h" // struct map_session_data #include "script.h" // struct script_reg, struct script_regstr +#include "searchstore.h" // struct s_search_store_info #include "status.h" // OPTION_*, struct weapon_atk #include "unit.h" // unit_stop_attack(), unit_stop_walking() #include "vending.h" // struct s_vending @@ -104,7 +106,6 @@ struct map_session_data { unsigned gangsterparadise : 1; unsigned rest : 1; unsigned storage_flag : 2; //0: closed, 1: Normal Storage open, 2: guild storage open [Skotlex] - unsigned snovice_call_flag : 2; //Summon Angel (stage 1~3) unsigned snovice_dead_flag : 1; //Explosion spirits on death: 0 off, 1 used. unsigned abra_flag : 1; // Abracadabra bugfix by Aru unsigned autocast : 1; // Autospell flag [Inkfish] @@ -128,6 +129,7 @@ struct map_session_data { unsigned doridori : 1; unsigned ignoreAll : 1; unsigned debug_remove_map : 1; // temporary state to track double remove_map's [FlavioJS] + unsigned buyingstore : 1; unsigned short autoloot; unsigned short autolootid; // [Zephyrus] unsigned noks : 3; // [Zeph Kill Steal Protection] @@ -356,6 +358,11 @@ struct map_session_data { char message[MESSAGE_SIZE]; struct s_vending vending[MAX_VENDING]; + unsigned int buyer_id; // uid of open buying store + struct s_buyingstore buyingstore; + + struct s_search_store_info searchstore; + struct pet_data *pd; struct homun_data *hd; // [blackhole89] struct mercenary_data *md; @@ -413,8 +420,8 @@ struct map_session_data { const char* debug_func; }; -//Update this max as necessary. 54 is the value needed for Super Baby currently -#define MAX_SKILL_TREE 54 +//Update this max as necessary. 55 is the value needed for Super Baby currently +#define MAX_SKILL_TREE 55 //Total number of classes (for data storage) #define CLASS_COUNT (JOB_MAX - JOB_NOVICE_HIGH + JOB_MAX_BASIC) @@ -515,9 +522,9 @@ extern int duel_count; #define pc_setsit(sd) ( (sd)->state.dead_sit = (sd)->vd.dead_sit = 2 ) #define pc_isdead(sd) ( (sd)->state.dead_sit == 1 ) #define pc_issit(sd) ( (sd)->vd.dead_sit == 2 ) -#define pc_isidle(sd) ( (sd)->chatID || (sd)->vender_id || DIFF_TICK(last_tick, (sd)->idletime) >= battle_config.idle_no_share ) -#define pc_istrading(sd) ( (sd)->npc_id || (sd)->vender_id || (sd)->state.trading ) -#define pc_cant_act(sd) ( (sd)->npc_id || (sd)->vender_id || (sd)->chatID || (sd)->sc.opt1 || (sd)->state.trading || (sd)->state.storage_flag ) +#define pc_isidle(sd) ( (sd)->chatID || (sd)->vender_id || (sd)->state.buyingstore || DIFF_TICK(last_tick, (sd)->idletime) >= battle_config.idle_no_share ) +#define pc_istrading(sd) ( (sd)->npc_id || (sd)->vender_id || (sd)->state.buyingstore || (sd)->state.trading ) +#define pc_cant_act(sd) ( (sd)->npc_id || (sd)->vender_id || (sd)->state.buyingstore || (sd)->chatID || (sd)->sc.opt1 || (sd)->state.trading || (sd)->state.storage_flag ) #define pc_setdir(sd,b,h) ( (sd)->ud.dir = (b) ,(sd)->head_dir = (h) ) #define pc_setchatid(sd,n) ( (sd)->chatID = n ) #define pc_ishiding(sd) ( (sd)->sc.option&(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK) ) diff --git a/src/map/script.c b/src/map/script.c index f842b614a..dec4a3f89 100644 --- a/src/map/script.c +++ b/src/map/script.c @@ -483,7 +483,14 @@ static void script_reportdata(struct script_data* data) break; case C_STR: case C_CONSTSTR:// string - ShowDebug("Data: string value=\"%s\"\n", data->u.str); + if( data->u.str ) + { + ShowDebug("Data: string value=\"%s\"\n", data->u.str); + } + else + { + ShowDebug("Data: string value=NULL\n"); + } break; case C_NAME:// reference if( reference_tovariable(data) ) @@ -1253,7 +1260,9 @@ const char* parse_curly_close(const char* p) set_label(l,script_pos, p); linkdb_final(&syntax.curly[pos].case_label); // free the list of case label syntax.curly_count--; - return p+1; + // if, for , while の閉じ判定 + p = parse_syntax_close(p + 1); + return p; } else { disp_error_message("parse_curly_close: unexpected string",p); return p + 1; @@ -1341,10 +1350,8 @@ const char* parse_syntax(const char* p) v = p2-p; // length of word at p2 memcpy(label,p,v); label[v]='\0'; - v = search_str(label); - if (v < 0 || str_data[v].type != C_INT) + if( !script_get_constant(label, &v) ) disp_error_message("parse_syntax: 'case' label not integer",p); - v = str_data[v].val; p = skip_word(p); } else { //Numeric value if((*p == '-' || *p == '+') && ISDIGIT(p[1])) // pre-skip because '-' can not skip_word @@ -1916,6 +1923,40 @@ static void add_buildin_func(void) } } +/// Retrieves the value of a constant. +bool script_get_constant(const char* name, int* value) +{ + int n = search_str(name); + + if( n == -1 || str_data[n].type != C_INT ) + {// not found or not a constant + return false; + } + value[0] = str_data[n].val; + + return true; +} + +/// Creates new constant or parameter with given value. +void script_set_constant(const char* name, int value, bool isparameter) +{ + int n = add_str(name); + + if( str_data[n].type == C_NOP ) + {// new + str_data[n].type = isparameter ? C_PARAM : C_INT; + str_data[n].val = value; + } + else if( str_data[n].type == C_PARAM || str_data[n].type == C_INT ) + {// existing parameter or constant + ShowError("script_set_constant: Attempted to overwrite existing %s '%s' (old value=%d, new value=%d).\n", ( str_data[n].type == C_PARAM ) ? "parameter" : "constant", name, str_data[n].val, value); + } + else + {// existing name + ShowError("script_set_constant: Invalid name for %s '%s' (already defined as %s).\n", isparameter ? "parameter" : "constant", name, script_op2name(str_data[n].type)); + } +} + /*========================================== * 定数データベースの読み込み *------------------------------------------*/ @@ -1923,7 +1964,7 @@ static void read_constdb(void) { FILE *fp; char line[1024],name[1024],val[1024]; - int n,type; + int type; sprintf(line, "%s/const.txt", db_path); fp=fopen(line, "r"); @@ -1938,12 +1979,7 @@ static void read_constdb(void) type=0; if(sscanf(line,"%[A-Za-z0-9_],%[-0-9xXA-Fa-f],%d",name,val,&type)>=2 || sscanf(line,"%[A-Za-z0-9_] %[-0-9xXA-Fa-f] %d",name,val,&type)>=2){ - n=add_str(name); - if(type==0) - str_data[n].type=C_INT; - else - str_data[n].type=C_PARAM; - str_data[n].val= (int)strtol(val,NULL,0); + script_set_constant(name, (int)strtol(val, NULL, 0), (bool)type); } } fclose(fp); @@ -5336,6 +5372,7 @@ BUILDIN_FUNC(countitem) { int nameid, i; int count = 0; + struct item_data* id = NULL; struct script_data* data; TBL_PC* sd = script_rid2sd(st); @@ -5345,24 +5382,26 @@ BUILDIN_FUNC(countitem) } data = script_getdata(st,2); - get_val(st,data); - if( data_isstring(data) ) { - const char* name = conv_str(st,data); - struct item_data* item_data; - if((item_data = itemdb_searchname(name)) != NULL) - nameid = item_data->nameid; - else - nameid = 0; - } else - nameid = conv_num(st,data); + get_val(st, data); // convert into value in case of a variable - if (nameid < 500) { - ShowError("wrong item ID : countitem(%i)\n", nameid); - script_reportsrc(st); + if( data_isstring(data) ) + {// item name + id = itemdb_searchname(conv_str(st, data)); + } + else + {// item id + id = itemdb_exists(conv_num(st, data)); + } + + if( id == NULL ) + { + ShowError("buildin_countitem: Invalid item '%s'.\n", script_getstr(st,2)); // returns string, regardless of what it was script_pushint(st,0); return 1; } + nameid = id->nameid; + for(i = 0; i < MAX_INVENTORY; i++) if(sd->status.inventory[i].nameid == nameid) count += sd->status.inventory[i].amount; @@ -5379,7 +5418,8 @@ BUILDIN_FUNC(countitem2) { int nameid, iden, ref, attr, c1, c2, c3, c4; int count = 0; - int i; + int i; + struct item_data* id = NULL; struct script_data* data; TBL_PC* sd = script_rid2sd(st); @@ -5387,19 +5427,27 @@ BUILDIN_FUNC(countitem2) script_pushint(st,0); return 0; } - + data = script_getdata(st,2); - get_val(st,data); - if( data_isstring(data) ) { - const char* name = conv_str(st,data); - struct item_data* item_data; - if((item_data = itemdb_searchname(name)) != NULL) - nameid = item_data->nameid; - else - nameid = 0; - } else - nameid = conv_num(st,data); - + get_val(st, data); // convert into value in case of a variable + + if( data_isstring(data) ) + {// item name + id = itemdb_searchname(conv_str(st, data)); + } + else + {// item id + id = itemdb_exists(conv_num(st, data)); + } + + if( id == NULL ) + { + ShowError("buildin_countitem2: Invalid item '%s'.\n", script_getstr(st,2)); // returns string, regardless of what it was + script_pushint(st,0); + return 1; + } + + nameid = id->nameid; iden = script_getnum(st,3); ref = script_getnum(st,4); attr = script_getnum(st,5); @@ -5407,13 +5455,7 @@ BUILDIN_FUNC(countitem2) c2 = (short)script_getnum(st,7); c3 = (short)script_getnum(st,8); c4 = (short)script_getnum(st,9); - - if (nameid < 500) { - ShowError("wrong item ID : countitem2(%i)\n", nameid); - script_pushint(st,0); - return 1; - } - + for(i = 0; i < MAX_INVENTORY; i++) if (sd->status.inventory[i].nameid > 0 && sd->inventory_data[i] != NULL && sd->status.inventory[i].amount > 0 && sd->status.inventory[i].nameid == nameid && @@ -5652,7 +5694,7 @@ BUILDIN_FUNC(getitem2) if (item_data == NULL) return -1; if(item_data->type==IT_WEAPON || item_data->type==IT_ARMOR){ - if(ref > 10) ref = 10; + if(ref > MAX_REFINE) ref = MAX_REFINE; } else if(item_data->type==IT_PETEGG) { iden = 1; @@ -9089,6 +9131,11 @@ BUILDIN_FUNC(birthpet) if( sd == NULL ) return 0; + if( sd->status.pet_id ) + {// do not send egg list, when you already have a pet + return 0; + } + clif_sendegg(sd); return 0; } @@ -9745,7 +9792,7 @@ BUILDIN_FUNC(pvpon) return 0; // nothing to do map[m].flag.pvp = 1; - clif_send0199(m,1); + clif_map_property_mapall(m, MAPPROPERTY_FREEPVPZONE); if(battle_config.pk_mode) // disable ranking functions if pk_mode is on [Valaris] return 0; @@ -9790,7 +9837,7 @@ BUILDIN_FUNC(pvpoff) return 0; //fixed Lupus map[m].flag.pvp = 0; - clif_send0199(m,0); + clif_map_property_mapall(m, MAPPROPERTY_NOTHING); if(battle_config.pk_mode) // disable ranking options if pk_mode is on [Valaris] return 0; @@ -9808,7 +9855,7 @@ BUILDIN_FUNC(gvgon) m = map_mapname2mapid(str); if(m >= 0 && !map[m].flag.gvg) { map[m].flag.gvg = 1; - clif_send0199(m,3); + clif_map_property_mapall(m, MAPPROPERTY_AGITZONE); } return 0; @@ -9822,7 +9869,7 @@ BUILDIN_FUNC(gvgoff) m = map_mapname2mapid(str); if(m >= 0 && map[m].flag.gvg) { map[m].flag.gvg = 0; - clif_send0199(m,0); + clif_map_property_mapall(m, MAPPROPERTY_NOTHING); } return 0; @@ -11773,17 +11820,17 @@ BUILDIN_FUNC(message) BUILDIN_FUNC(npctalk) { const char* str; - char message[255]; + char name[NAME_LENGTH], message[256]; struct npc_data* nd = (struct npc_data *)map_id2bl(st->oid); str = script_getstr(st,2); - if(nd) { - memcpy(message, nd->name, NAME_LENGTH); - strtok(message, "#"); // discard extra name identifier if present - strcat(message, " : "); - strncat(message, str, 254); //Prevent overflow possibility. [Skotlex] - clif_message(&(nd->bl), message); + if(nd) + { + safestrncpy(name, nd->name, sizeof(name)); + strtok(name, "#"); // discard extra name identifier if present + safesnprintf(message, sizeof(message), "%s : %s", name, str); + clif_message(&nd->bl, message); } return 0; @@ -12357,9 +12404,20 @@ BUILDIN_FUNC(autoequip) struct item_data *item_data; nameid=script_getnum(st,2); flag=script_getnum(st,3); - if(nameid>=500 && (item_data = itemdb_exists(nameid)) != NULL){ - item_data->flag.autoequip = flag>0?1:0; + + if( ( item_data = itemdb_exists(nameid) ) == NULL ) + { + ShowError("buildin_autoequip: Invalid item '%d'.\n", nameid); + return 1; + } + + if( !itemdb_isequip2(item_data) ) + { + ShowError("buildin_autoequip: Item '%d' cannot be equipped.\n", nameid); + return 1; } + + item_data->flag.autoequip = flag>0?1:0; return 0; } @@ -14635,9 +14693,6 @@ static int buildin_mobuseskill_sub(struct block_list *bl,va_list ap) if( md->class_ != mobid ) return 0; - if( md->ud.skilltimer != INVALID_TIMER ) // Cancel the casting skill. - unit_skillcastcancel(bl,0); - // 0:self, 1:target, 2:master, default:random switch( target ) { @@ -14650,6 +14705,9 @@ static int buildin_mobuseskill_sub(struct block_list *bl,va_list ap) if( !tbl ) return 0; + if( md->ud.skilltimer != INVALID_TIMER ) // Cancel the casting skill. + unit_skillcastcancel(bl,0); + if( skill_get_casttype(skillid) == CAST_GROUND ) unit_skilluse_pos2(&md->bl, tbl->x, tbl->y, skillid, skilllv, casttime, cancel); else @@ -14756,6 +14814,56 @@ BUILDIN_FUNC(pushpc) return 0; } + +/// Invokes buying store preparation window +/// buyingstore <slots>; +BUILDIN_FUNC(buyingstore) +{ + struct map_session_data* sd; + + if( ( sd = script_rid2sd(st) ) == NULL ) + { + return 0; + } + + buyingstore_setup(sd, script_getnum(st,2)); + return 0; +} + + +/// Invokes search store info window +/// searchstores <uses>,<effect>; +BUILDIN_FUNC(searchstores) +{ + unsigned short effect; + unsigned int uses; + struct map_session_data* sd; + + if( ( sd = script_rid2sd(st) ) == NULL ) + { + return 0; + } + + uses = script_getnum(st,2); + effect = script_getnum(st,3); + + if( !uses ) + { + ShowError("buildin_searchstores: Amount of uses cannot be zero.\n"); + return 1; + } + + if( effect > 1 ) + { + ShowError("buildin_searchstores: Invalid effect id %hu, specified.\n", effect); + return 1; + } + + searchstore_open(sd, uses, effect); + return 0; +} + + // declarations that were supposed to be exported from npc_chat.c #ifdef PCRE_SUPPORT BUILDIN_FUNC(defpattern); @@ -15117,6 +15225,8 @@ struct script_function buildin_func[] = { BUILDIN_DEF(areamobuseskill,"siiiiviiiii"), BUILDIN_DEF(progressbar,"si"), BUILDIN_DEF(pushpc,"ii"), + BUILDIN_DEF(buyingstore,"i"), + BUILDIN_DEF(searchstores,"ii"), // WoE SE BUILDIN_DEF(agitstart2,""), BUILDIN_DEF(agitend2,""), diff --git a/src/map/script.h b/src/map/script.h index d6ede0e8b..c272f2d32 100644 --- a/src/map/script.h +++ b/src/map/script.h @@ -167,6 +167,9 @@ struct DBMap* script_get_label_db(void); struct DBMap* script_get_userfunc_db(void); void script_run_autobonus(const char *autobonus,int id, int pos); +bool script_get_constant(const char* name, int* value); +void script_set_constant(const char* name, int value, bool isparameter); + void script_cleararray_pc(struct map_session_data* sd, const char* varname, void* value); void script_setarray_pc(struct map_session_data* sd, const char* varname, uint8 idx, void* value, int* refcache); diff --git a/src/map/searchstore.c b/src/map/searchstore.c new file mode 100644 index 000000000..d7378ab36 --- /dev/null +++ b/src/map/searchstore.c @@ -0,0 +1,405 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "../common/cbasetypes.h" +#include "../common/malloc.h" // aMalloc, aRealloc, aFree +#include "../common/showmsg.h" // ShowError, ShowWarning +#include "../common/strlib.h" // safestrncpy +#include "battle.h" // battle_config.* +#include "clif.h" // clif_open_search_store_info, clif_search_store_info_* +#include "pc.h" // struct map_session_data +#include "searchstore.h" // struct s_search_store_info + + +/// failure constants for clif functions +enum e_searchstore_failure +{ + SSI_FAILED_NOTHING_SEARCH_ITEM = 0, // "No matching stores were found." + SSI_FAILED_OVER_MAXCOUNT = 1, // "There are too many results. Please enter more detailed search term." + SSI_FAILED_SEARCH_CNT = 2, // "You cannot search anymore." + SSI_FAILED_LIMIT_SEARCH_TIME = 3, // "You cannot search yet." + SSI_FAILED_SSILIST_CLICK_TO_OPEN_STORE = 4, // "No sale (purchase) information available." +}; + + +enum e_searchstore_searchtype +{ + SEARCHTYPE_VENDING = 0, + SEARCHTYPE_BUYING_STORE = 1, +}; + + +enum e_searchstore_effecttype +{ + EFFECTTYPE_NORMAL = 0, + EFFECTTYPE_CASH = 1, + EFFECTTYPE_MAX +}; + + +/// type for shop search function +typedef bool (*searchstore_search_t)(struct map_session_data* sd, unsigned short nameid); +typedef bool (*searchstore_searchall_t)(struct map_session_data* sd, const struct s_search_store_search* s); + + +/// retrieves search function by type +static searchstore_search_t searchstore_getsearchfunc(unsigned char type) +{ + switch( type ) + { + case SEARCHTYPE_VENDING: return &vending_search; + case SEARCHTYPE_BUYING_STORE: return &buyingstore_search; + } + return NULL; +} + + +/// retrieves search-all function by type +static searchstore_searchall_t searchstore_getsearchallfunc(unsigned char type) +{ + switch( type ) + { + case SEARCHTYPE_VENDING: return &vending_searchall; + case SEARCHTYPE_BUYING_STORE: return &buyingstore_searchall; + } + return NULL; +} + + +/// checks if the player has a store by type +static bool searchstore_hasstore(struct map_session_data* sd, unsigned char type) +{ + switch( type ) + { + case SEARCHTYPE_VENDING: return (bool)( sd->vender_id != 0 ); + case SEARCHTYPE_BUYING_STORE: return sd->state.buyingstore; + } + return false; +} + + +/// returns player's store id by type +static int searchstore_getstoreid(struct map_session_data* sd, unsigned char type) +{ + switch( type ) + { + case SEARCHTYPE_VENDING: return sd->vender_id; + case SEARCHTYPE_BUYING_STORE: return sd->buyer_id; + } + return 0; +} + + +bool searchstore_open(struct map_session_data* sd, unsigned int uses, unsigned short effect) +{ + if( !battle_config.feature_search_stores || sd->searchstore.open ) + { + return false; + } + + if( !uses || effect >= EFFECTTYPE_MAX ) + {// invalid input + return false; + } + + sd->searchstore.open = true; + sd->searchstore.uses = uses; + sd->searchstore.effect = effect; + + clif_open_search_store_info(sd); + + return true; +} + + +void searchstore_query(struct map_session_data* sd, unsigned char type, unsigned int min_price, unsigned int max_price, const unsigned short* itemlist, unsigned int item_count, const unsigned short* cardlist, unsigned int card_count) +{ + unsigned int i; + struct map_session_data* pl_sd; + struct s_mapiterator* iter; + struct s_search_store_search s; + searchstore_searchall_t store_searchall; + time_t querytime; + + if( !battle_config.feature_search_stores ) + { + return; + } + + if( !sd->searchstore.open ) + { + return; + } + + if( ( store_searchall = searchstore_getsearchallfunc(type) ) == NULL ) + { + ShowError("searchstore_query: Unknown search type %u (account_id=%d).\n", (unsigned int)type, sd->bl.id); + return; + } + + time(&querytime); + + if( sd->searchstore.nextquerytime > querytime ) + { + clif_search_store_info_failed(sd, SSI_FAILED_LIMIT_SEARCH_TIME); + return; + } + + if( !sd->searchstore.uses ) + { + clif_search_store_info_failed(sd, SSI_FAILED_SEARCH_CNT); + return; + } + + // validate lists + for( i = 0; i < item_count; i++ ) + { + if( !itemdb_exists(itemlist[i]) ) + { + ShowWarning("searchstore_query: Client resolved item %hu is not known.\n", itemlist[i]); + clif_search_store_info_failed(sd, SSI_FAILED_NOTHING_SEARCH_ITEM); + return; + } + } + for( i = 0; i < card_count; i++ ) + { + if( !itemdb_exists(cardlist[i]) ) + { + ShowWarning("searchstore_query: Client resolved card %hu is not known.\n", cardlist[i]); + clif_search_store_info_failed(sd, SSI_FAILED_NOTHING_SEARCH_ITEM); + return; + } + } + + if( max_price < min_price ) + { + swap(min_price, max_price); + } + + sd->searchstore.uses--; + sd->searchstore.type = type; + sd->searchstore.nextquerytime = querytime+battle_config.searchstore_querydelay; + + // drop previous results + searchstore_clear(sd); + + // allocate max. amount of results + sd->searchstore.items = aMalloc(sizeof(struct s_search_store_info_item)*battle_config.searchstore_maxresults); + + // search + s.search_sd = sd; + s.itemlist = itemlist; + s.cardlist = cardlist; + s.item_count = item_count; + s.card_count = card_count; + s.min_price = min_price; + s.max_price = max_price; + iter = mapit_geteachpc(); + + for( pl_sd = (struct map_session_data*)mapit_first(iter); mapit_exists(iter); pl_sd = (struct map_session_data*)mapit_next(iter) ) + { + if( sd == pl_sd ) + {// skip own shop, if any + continue; + } + + if( !store_searchall(pl_sd, &s) ) + {// exceeded result size + clif_search_store_info_failed(sd, SSI_FAILED_OVER_MAXCOUNT); + break; + } + } + + mapit_free(iter); + + if( sd->searchstore.count ) + { + // reclaim unused memory + sd->searchstore.items = aRealloc(sd->searchstore.items, sizeof(struct s_search_store_info_item)*sd->searchstore.count); + + // present results + clif_search_store_info_ack(sd); + + // one page displayed + sd->searchstore.pages++; + } + else + { + // cleanup + searchstore_clear(sd); + + // update uses + clif_search_store_info_ack(sd); + + // notify of failure + clif_search_store_info_failed(sd, SSI_FAILED_NOTHING_SEARCH_ITEM); + } +} + + +/// checks whether or not more results are available for the client +bool searchstore_querynext(struct map_session_data* sd) +{ + if( sd->searchstore.count && ( sd->searchstore.count-1 )/SEARCHSTORE_RESULTS_PER_PAGE < sd->searchstore.pages ) + { + return true; + } + + return false; +} + + +void searchstore_next(struct map_session_data* sd) +{ + if( !battle_config.feature_search_stores || !sd->searchstore.open || sd->searchstore.count <= sd->searchstore.pages*SEARCHSTORE_RESULTS_PER_PAGE ) + {// nothing (more) to display + return; + } + + // present results + clif_search_store_info_ack(sd); + + // one more page displayed + sd->searchstore.pages++; +} + + +void searchstore_clear(struct map_session_data* sd) +{ + searchstore_clearremote(sd); + + if( sd->searchstore.items ) + {// release results + aFree(sd->searchstore.items); + sd->searchstore.items = NULL; + } + + sd->searchstore.count = 0; + sd->searchstore.pages = 0; +} + + +void searchstore_close(struct map_session_data* sd) +{ + if( sd->searchstore.open ) + { + searchstore_clear(sd); + + sd->searchstore.uses = 0; + sd->searchstore.open = false; + } +} + + +void searchstore_click(struct map_session_data* sd, int account_id, int store_id, unsigned short nameid) +{ + unsigned int i; + struct map_session_data* pl_sd; + searchstore_search_t store_search; + + if( !battle_config.feature_search_stores || !sd->searchstore.open || !sd->searchstore.count ) + { + return; + } + + searchstore_clearremote(sd); + + ARR_FIND( 0, sd->searchstore.count, i, sd->searchstore.items[i].store_id == store_id && sd->searchstore.items[i].account_id == account_id && sd->searchstore.items[i].nameid == nameid ); + if( i == sd->searchstore.count ) + {// no such result, crafted + ShowWarning("searchstore_click: Received request with item %hu of account %d, which is not part of current result set (account_id=%d, char_id=%d).\n", nameid, account_id, sd->bl.id, sd->status.char_id); + clif_search_store_info_failed(sd, SSI_FAILED_SSILIST_CLICK_TO_OPEN_STORE); + return; + } + + if( ( pl_sd = map_id2sd(account_id) ) == NULL ) + {// no longer online + clif_search_store_info_failed(sd, SSI_FAILED_SSILIST_CLICK_TO_OPEN_STORE); + return; + } + + if( !searchstore_hasstore(pl_sd, sd->searchstore.type) || searchstore_getstoreid(pl_sd, sd->searchstore.type) != store_id ) + {// no longer vending/buying or not same shop + clif_search_store_info_failed(sd, SSI_FAILED_SSILIST_CLICK_TO_OPEN_STORE); + return; + } + + store_search = searchstore_getsearchfunc(sd->searchstore.type); + + if( !store_search(pl_sd, nameid) ) + {// item no longer being sold/bought + clif_search_store_info_failed(sd, SSI_FAILED_SSILIST_CLICK_TO_OPEN_STORE); + return; + } + + switch( sd->searchstore.effect ) + { + case EFFECTTYPE_NORMAL: + // display coords + + if( sd->bl.m != pl_sd->bl.m ) + {// not on same map, wipe previous marker + clif_search_store_info_click_ack(sd, -1, -1); + } + else + { + clif_search_store_info_click_ack(sd, pl_sd->bl.x, pl_sd->bl.y); + } + + break; + case EFFECTTYPE_CASH: + // open remotely + + // to bypass range checks + sd->searchstore.remote_id = account_id; + + switch( sd->searchstore.type ) + { + case SEARCHTYPE_VENDING: vending_vendinglistreq(sd, account_id); break; + case SEARCHTYPE_BUYING_STORE: buyingstore_open(sd, account_id); break; + } + + break; + default: + // unknown + ShowError("searchstore_click: Unknown search store effect %u (account_id=%d).\n", (unsigned int)sd->searchstore.effect, sd->bl.id); + } +} + + +/// checks whether or not sd has opened account_id's shop remotely +bool searchstore_queryremote(struct map_session_data* sd, int account_id) +{ + return (bool)( sd->searchstore.open && sd->searchstore.count && sd->searchstore.remote_id == account_id ); +} + + +/// removes range-check bypassing for remotely opened stores +void searchstore_clearremote(struct map_session_data* sd) +{ + sd->searchstore.remote_id = 0; +} + + +/// receives results from a store-specific callback +bool searchstore_result(struct map_session_data* sd, int store_id, int account_id, const char* store_name, unsigned short nameid, unsigned short amount, unsigned int price, const short* card, unsigned char refine) +{ + struct s_search_store_info_item* ssitem; + + if( sd->searchstore.count >= (unsigned int)battle_config.searchstore_maxresults ) + {// no more + return false; + } + + ssitem = &sd->searchstore.items[sd->searchstore.count++]; + ssitem->store_id = store_id; + ssitem->account_id = account_id; + safestrncpy(ssitem->store_name, store_name, sizeof(ssitem->store_name)); + ssitem->nameid = nameid; + ssitem->amount = amount; + ssitem->price = price; + memcpy(ssitem->card, card, sizeof(ssitem->card)); + ssitem->refine = refine; + + return true; +} diff --git a/src/map/searchstore.h b/src/map/searchstore.h new file mode 100644 index 000000000..ffa8e9784 --- /dev/null +++ b/src/map/searchstore.h @@ -0,0 +1,57 @@ +// Copyright (c) Athena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef _SEARCHSTORE_H_ +#define _SEARCHSTORE_H_ + +#define SEARCHSTORE_RESULTS_PER_PAGE 10 + +/// information about the search being performed +struct s_search_store_search +{ + struct map_session_data* search_sd; // sd of the searching player + const unsigned short* itemlist; + const unsigned short* cardlist; + unsigned int item_count; + unsigned int card_count; + unsigned int min_price; + unsigned int max_price; +}; + +struct s_search_store_info_item +{ + int store_id; + int account_id; + char store_name[MESSAGE_SIZE]; + unsigned short nameid; + unsigned short amount; + unsigned int price; + short card[MAX_SLOTS]; + unsigned char refine; +}; + +struct s_search_store_info +{ + unsigned int count; + struct s_search_store_info_item* items; + unsigned int pages; // amount of pages already sent to client + unsigned int uses; + int remote_id; + time_t nextquerytime; + unsigned short effect; // 0 = Normal (display coords), 1 = Cash (remote open store) + unsigned char type; // 0 = Vending, 1 = Buying Store + bool open; +}; + +bool searchstore_open(struct map_session_data* sd, unsigned int uses, unsigned short effect); +void searchstore_query(struct map_session_data* sd, unsigned char type, unsigned int min_price, unsigned int max_price, const unsigned short* itemlist, unsigned int item_count, const unsigned short* cardlist, unsigned int card_count); +bool searchstore_querynext(struct map_session_data* sd); +void searchstore_next(struct map_session_data* sd); +void searchstore_clear(struct map_session_data* sd); +void searchstore_close(struct map_session_data* sd); +void searchstore_click(struct map_session_data* sd, int account_id, int store_id, unsigned short nameid); +bool searchstore_queryremote(struct map_session_data* sd, int account_id); +void searchstore_clearremote(struct map_session_data* sd); +bool searchstore_result(struct map_session_data* sd, int store_id, int account_id, const char* store_name, unsigned short nameid, unsigned short amount, unsigned int price, const short* card, unsigned char refine); + +#endif // _SEARCHSTORE_H_ diff --git a/src/map/skill.c b/src/map/skill.c index cd9115df4..160beeaef 100644 --- a/src/map/skill.c +++ b/src/map/skill.c @@ -5692,6 +5692,12 @@ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, in case ALL_WEWISH: clif_skill_nodamage(src,bl,skillid,skilllv,1); break; + case ALL_BUYING_STORE: + if( sd ) + {// players only, skill allows 5 buying slots + clif_skill_nodamage(src, bl, skillid, skilllv, buyingstore_setup(sd, MAX_BUYINGSTORE_SLOTS)); + } + break; default: ShowWarning("skill_castend_nodamage_id: Unknown skill used:%d\n",skillid); clif_skill_nodamage(src,bl,skillid,skilllv,1); @@ -6754,15 +6760,23 @@ int skill_dance_overlap(struct skill_unit* unit, int flag) *------------------------------------------*/ static bool skill_dance_switch(struct skill_unit* unit, int flag) { + static int prevflag = 1; // by default the backup is empty static struct skill_unit_group backup; struct skill_unit_group* group = unit->group; - //TODO: add protection against attempts to read an empty backup / write to a full backup - // val2&UF_ENSEMBLE is a hack to indicate dissonance if ( !(group->state.song_dance&0x1 && unit->val2&UF_ENSEMBLE) ) return false; + if( flag == prevflag ) + {// protection against attempts to read an empty backup / write to a full backup + ShowError("skill_dance_switch: Attempted to %s (skill_id=%d, skill_lv=%d, src_id=%d).\n", + flag ? "read an empty backup" : "write to a full backup", + group->skill_id, group->skill_lv, group->src_id); + return false; + } + prevflag = flag; + if( !flag ) { //Transform int skillid = unit->val2&UF_SONG ? BA_DISSONANCE : DC_UGLYDANCE; diff --git a/src/map/status.c b/src/map/status.c index 45fe3a4cf..33da5fb14 100644 --- a/src/map/status.c +++ b/src/map/status.c @@ -174,7 +174,7 @@ void initChangeTables(void) set_sc( BS_ADRENALINE , SC_ADRENALINE , SI_ADRENALINE , SCB_ASPD ); set_sc( BS_WEAPONPERFECT , SC_WEAPONPERFECTION, SI_WEAPONPERFECTION, SCB_NONE ); set_sc( BS_OVERTHRUST , SC_OVERTHRUST , SI_OVERTHRUST , SCB_NONE ); - set_sc( BS_MAXIMIZE , SC_MAXIMIZEPOWER , SI_MAXIMIZEPOWER , SCB_NONE ); + set_sc( BS_MAXIMIZE , SC_MAXIMIZEPOWER , SI_MAXIMIZEPOWER , SCB_REGEN ); add_sc( HT_LANDMINE , SC_STUN ); add_sc( HT_ANKLESNARE , SC_ANKLE ); add_sc( HT_SANDMAN , SC_SLEEP ); @@ -2693,6 +2693,7 @@ void status_calc_regen_rate(struct block_list *bl, struct regen_data *regen, str (((TBL_PC*)bl)->class_&MAPID_UPPERMASK) == MAPID_MONK && (sc->data[SC_EXTREMITYFIST] || (sc->data[SC_EXPLOSIONSPIRITS] && (!sc->data[SC_SPIRIT] || sc->data[SC_SPIRIT]->val2 != SL_MONK))) ) + || sc->data[SC_MAXIMIZEPOWER] ) //No natural SP regen regen->flag &=~RGN_SP; @@ -6746,7 +6747,8 @@ int status_change_end(struct block_list* bl, enum sc_type type, int tid) case SC_AUTOTRADE: if (tid == INVALID_TIMER) break; - vending_closevending(sd); + // Note: vending/buying is closed by unit_remove_map, no + // need to do it here. map_quit(sd); // Because map_quit calls status_change_end with tid -1 // from here it's not neccesary to continue diff --git a/src/map/trade.c b/src/map/trade.c index ba01826f2..bedbb9451 100644 --- a/src/map/trade.c +++ b/src/map/trade.c @@ -138,8 +138,8 @@ void trade_tradeack(struct map_session_data *sd, int type) } //Check if you can start trade. - if (sd->npc_id || sd->vender_id || sd->state.storage_flag || - tsd->npc_id || tsd->vender_id || tsd->state.storage_flag) + if (sd->npc_id || sd->vender_id || sd->state.buyingstore || sd->state.storage_flag || + tsd->npc_id || tsd->vender_id || tsd->state.buyingstore || tsd->state.storage_flag) { //Fail clif_tradestart(sd, 2); clif_tradestart(tsd, 2); diff --git a/src/map/unit.c b/src/map/unit.c index 7abfde12b..2467baf86 100644 --- a/src/map/unit.c +++ b/src/map/unit.c @@ -803,6 +803,7 @@ int unit_can_move(struct block_list *bl) if (sd && ( pc_issit(sd) || sd->vender_id || + sd->state.buyingstore || sd->state.blockedmove )) return 0; //Can't move @@ -1872,6 +1873,8 @@ int unit_remove_map_(struct block_list *bl, clr_type clrtype, const char* file, trade_tradecancel(sd); if(sd->vender_id) vending_closevending(sd); + buyingstore_close(sd); + searchstore_close(sd); if(sd->state.storage_flag == 1) storage_storage_quit(sd,0); else if (sd->state.storage_flag == 2) diff --git a/src/map/vending.c b/src/map/vending.c index a47be9057..08e15d733 100644 --- a/src/map/vending.c +++ b/src/map/vending.c @@ -87,8 +87,11 @@ void vending_purchasereq(struct map_session_data* sd, int aid, int uid, const ui return; } - if( sd->bl.m != vsd->bl.m || !check_distance_bl(&sd->bl, &vsd->bl, AREA_SIZE) ) + if( !searchstore_queryremote(sd, aid) && ( sd->bl.m != vsd->bl.m || !check_distance_bl(&sd->bl, &vsd->bl, AREA_SIZE) ) ) return; // shop too far away + + searchstore_clearremote(sd); + if( count < 1 || count > MAX_VENDING || count > vsd->vend_num ) return; // invalid amount of purchased items @@ -314,3 +317,88 @@ void vending_openvending(struct map_session_data* sd, const char* message, bool clif_openvending(sd,sd->bl.id,sd->vending); clif_showvendingboard(&sd->bl,message,0); } + + +/// Checks if an item is being sold in given player's vending. +bool vending_search(struct map_session_data* sd, unsigned short nameid) +{ + int i; + + if( !sd->vender_id ) + {// not vending + return false; + } + + ARR_FIND( 0, sd->vend_num, i, sd->status.cart[sd->vending[i].index].nameid == (short)nameid ); + if( i == sd->vend_num ) + {// not found + return false; + } + + return true; +} + + +/// Searches for all items in a vending, that match given ids, price and possible cards. +/// @return Whether or not the search should be continued. +bool vending_searchall(struct map_session_data* sd, const struct s_search_store_search* s) +{ + int i, c, slot; + unsigned int idx, cidx; + struct item* it; + + if( !sd->vender_id ) + {// not vending + return true; + } + + for( idx = 0; idx < s->item_count; idx++ ) + { + ARR_FIND( 0, sd->vend_num, i, sd->status.cart[sd->vending[i].index].nameid == (short)s->itemlist[idx] ); + if( i == sd->vend_num ) + {// not found + continue; + } + it = &sd->status.cart[sd->vending[i].index]; + + if( s->min_price && s->min_price > sd->vending[i].value ) + {// too low price + continue; + } + + if( s->max_price && s->max_price < sd->vending[i].value ) + {// too high price + continue; + } + + if( s->card_count ) + {// check cards + if( itemdb_isspecial(it->card[0]) ) + {// something, that is not a carded + continue; + } + slot = itemdb_slot(it->nameid); + + for( c = 0; c < slot && it->card[c]; c ++ ) + { + ARR_FIND( 0, s->card_count, cidx, s->cardlist[cidx] == it->card[c] ); + if( cidx != s->card_count ) + {// found + break; + } + } + + if( c == slot || !it->card[c] ) + {// no card match + continue; + } + } + + if( !searchstore_result(s->search_sd, sd->vender_id, sd->status.account_id, sd->message, it->nameid, sd->vending[i].amount, sd->vending[i].value, it->card, it->refine) ) + {// result set full + return false; + } + } + + return true; +} diff --git a/src/map/vending.h b/src/map/vending.h index 3c483a38c..6cfc90820 100644 --- a/src/map/vending.h +++ b/src/map/vending.h @@ -7,6 +7,7 @@ #include "../common/cbasetypes.h" //#include "map.h" struct map_session_data; +struct s_search_store_search; struct s_vending { short index; @@ -18,5 +19,7 @@ void vending_closevending(struct map_session_data* sd); void vending_openvending(struct map_session_data* sd, const char* message, bool flag, const uint8* data, int count); 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); +bool vending_searchall(struct map_session_data* sd, const struct s_search_store_search* s); #endif /* _VENDING_H_ */ diff --git a/src/plugins/console.c b/src/plugins/console.c index b1307f863..bf1a133ca 100644 --- a/src/plugins/console.c +++ b/src/plugins/console.c @@ -34,8 +34,7 @@ do{ \ DWORD dwThreadId; \ buf.worker = CreateThread(NULL, 0, worker_ ## name, NULL, CREATE_SUSPENDED, &dwThreadId); \ - if( errvar ) \ - *errvar = ( buf.worker == NULL ); \ + *(errvar) = ( buf.worker == NULL ); \ }while(0) /// Buffer for asynchronous input @@ -61,8 +60,7 @@ typedef struct _buffer { if( pid == 0 ){ \ worker_ ## name(); \ } \ - if( errvar ) \ - *errvar = (pid == -1); \ + *(errvar) = (pid == -1); \ }while(0) #define PIPE_READ 0 |