summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/char/char.c321
-rw-r--r--src/char_sql/char.c266
-rw-r--r--src/common/mmo.h9
-rw-r--r--src/common/socket.c18
-rw-r--r--src/common/socket.h2
-rw-r--r--src/common/strlib.c3
-rw-r--r--src/common/strlib.h6
-rw-r--r--src/common/utils.c72
-rw-r--r--src/common/utils.h3
-rw-r--r--src/login/account_sql.c2
-rw-r--r--src/login/login.c7
-rw-r--r--src/map/Makefile.in6
-rw-r--r--src/map/atcommand.c22
-rw-r--r--src/map/battle.c6
-rw-r--r--src/map/battle.h6
-rw-r--r--src/map/buyingstore.c470
-rw-r--r--src/map/buyingstore.h33
-rw-r--r--src/map/chat.c7
-rw-r--r--src/map/clif.c947
-rw-r--r--src/map/clif.h62
-rw-r--r--src/map/itemdb.c27
-rw-r--r--src/map/itemdb.h1
-rw-r--r--src/map/log.c14
-rw-r--r--src/map/log.h7
-rw-r--r--src/map/mail.c2
-rw-r--r--src/map/map.c1
-rw-r--r--src/map/npc.c9
-rw-r--r--src/map/party.c4
-rw-r--r--src/map/pc.c13
-rw-r--r--src/map/pc.h19
-rw-r--r--src/map/script.c234
-rw-r--r--src/map/script.h3
-rw-r--r--src/map/searchstore.c405
-rw-r--r--src/map/searchstore.h57
-rw-r--r--src/map/skill.c18
-rw-r--r--src/map/status.c6
-rw-r--r--src/map/trade.c4
-rw-r--r--src/map/unit.c3
-rw-r--r--src/map/vending.c90
-rw-r--r--src/map/vending.h3
-rw-r--r--src/plugins/console.c6
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