diff --git a/doc/script_commands.txt b/doc/script_commands.txt index 644cc8e802..5d4eb1d491 100644 --- a/doc/script_commands.txt +++ b/doc/script_commands.txt @@ -5274,6 +5274,66 @@ invoking character. --------------------------------------- +*mail ,"","","<body>"{,<zeny>{,<item id array>,<item amount array>{,<item card0 array>{,<item card1 array>{,<item card2 array>{,<item card3 array> + {,<random option id0 array>, <random option value0 array>, <random option paramter0 array>{,<random option id1 array>, <random option value1 array>, <random option paramter1 array> + {,<random option id2 array>, <random option value2 array>, <random option paramter2 array>{,<random option id3 array>, <random option value3 array>, <random option paramter3 array> + {,<random option id4 array>, <random option value4 array>, <random option paramter4 array>}}}}}}}; + +This command will send mail to the <destination id> which is a character ID. +A <sender name> can be specified but does not have to be from the direct creator +of the mail and is limited to NAME_LENGTH (24) characters. Mail <title> is limited +to MAIL_TITLE_LENGTH (40) characters. Mail <body> is limited to MAIL_BODY_LENGTH +(200) characters for PACKETVER < 20150513 or 500 characters for later clients. + +Optional <zeny> and item data can be added to the mail as well. PACKETVER < 20150513 +is limited to 1 item while later clients are limited to MAIL_MAX_ITEM (5). + +The <item id array>, <item amount array>, <item card0 array>, <item card1 array>, +<item card2 array>, and <item card3 array> should all be integer arrays. + +For random options there can be 5 arrays in pairs of 3 (ids, values, parameters) right after the cards. +All of these arrays shall be integer arrays as well. + +Example of sending mail with zeny: + .@charid = getcharid(0); + .@sender$ = "Poring"; + .@title$ = "Welcome"; + .@body$ = "Hi! I'm a simple Poring from the Prontera fields! Welcome to Ragnarok!"; + .@zeny = 5000; + mail .@charid, .@sender$, .@title$, .@body$, .@zeny; + +Example of sending mail with items: + .@charid = getcharid(0); + .@sender$ = "Angeling"; + .@title$ = "Welcome"; + .@body$ = "Hi! I'm a simple Angeling from the Prontera fields! Welcome to Ragnarok!"; + .@zeny = 0; + setarray .@mailitem[0], 504, 505, 2220, 1214; // White Potion, Blue Potion, Hat, Dagger + setarray .@mailamount[0], 10, 5, 1, 1; // 10 White Potions, 5 Blue Potions, 1 Hat, 1 Dagger + setarray .@mailcard0[0], 0, 0, 4198, 4092; // Attach Maya Purple Card to the Hat, Attach Skeleton Worker Card to Dagger + setarray .@mailcard1[0], 0, 0, 0, 4092; // Attach Skeleton Worker Card to Dagger + setarray .@mailcard2[0], 0, 0, 0, 4092; // Attach Skeleton Worker Card to Dagger + mail .@charid, .@sender$, .@title$, .@body$, .@zeny, .@mailitem, .@mailamount, .@mailcard0, .@mailcard1, .@mailcard2; + +Example of sending mail with items and random options: + .@charid = getcharid(0); + .@sender$ = "Angeling"; + .@title$ = "Welcome"; + .@body$ = "Hi! I'm a simple Angeling from the Prontera fields! Welcome to Ragnarok!"; + .@zeny = 0; + setarray .@mailitem[0], 504, 505, 2220, 1214; // White Potion, Blue Potion, Hat, Dagger + setarray .@mailamount[0], 10, 5, 1, 1; // 10 White Potions, 5 Blue Potions, 1 Hat, 1 Dagger + setarray .@mailcard0[0], 0, 0, 4198, 4092; // Attach Maya Purple Card to the Hat, Attach Skeleton Worker Card to Dagger + setarray .@mailcard1[0], 0, 0, 0, 4092; // Attach Skeleton Worker Card to Dagger + setarray .@mailcard2[0], 0, 0, 0, 4092; // Attach Skeleton Worker Card to Dagger + setarray .@mailcard3[0], 0, 0, 0, 0; // Empty last slot + setarray .@mailrndopt_id0[0], 0, 0, 0, 0, RDMOPT_VAR_MAXHPAMOUNT; // Enchant the Dagger with increased HP option + setarray .@mailrndopt_val0[0], 0, 0, 0, 0, 1000; // Enchant the Dagger with increased HP option by 1000 points + setarray .@mailrndopt_prm0[0], 0, 0, 0, 0, 0; // Enchant the Dagger with increased HP option - does not need any parameter + mail .@charid, .@sender$, .@title$, .@body$, .@zeny, .@mailitem, .@mailamount, .@mailcard0, .@mailcard1, .@mailcard2, .@mailcard3, .@mailrndopt_id0, .@mailrndopt_val0, .@mailrndopt_prm0; + +--------------------------------------- + *openauction({<char_id>}); This will open the Auction window on the client connected to the invoking character. diff --git a/src/char/int_mail.cpp b/src/char/int_mail.cpp index bf47fe94bb..ced2a4468c 100644 --- a/src/char/int_mail.cpp +++ b/src/char/int_mail.cpp @@ -484,7 +484,8 @@ void mapif_Mail_return(int fd, uint32 char_id, int mail_id) else if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `id` = '%d'", schema_config.mail_db, mail_id) || SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `id` = '%d'", schema_config.mail_attachment_db, mail_id) ) Sql_ShowDebug(sql_handle); - else + // If it was not sent by the server, since we do not want to return mails to the server + else if( msg.send_id != 0 ) { char temp_[MAIL_TITLE_LENGTH]; @@ -543,20 +544,38 @@ void mapif_parse_Mail_send(int fd) { struct mail_message msg; char esc_name[NAME_LENGTH*2+1]; + char *data; + size_t len; if(RFIFOW(fd,2) != 8 + sizeof(struct mail_message)) return; memcpy(&msg, RFIFOP(fd,8), sizeof(struct mail_message)); + if( msg.dest_id != 0 ){ + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `char_id`, `name` FROM `%s` WHERE `char_id` = '%u'", schema_config.char_db, msg.dest_id) ){ + Sql_ShowDebug(sql_handle); + return; + } + + msg.dest_id = 0; + msg.dest_name[0] = '\0'; + + if( SQL_SUCCESS == Sql_NextRow(sql_handle) ){ + Sql_GetData(sql_handle, 0, &data, NULL); + msg.dest_id = atoi(data); + Sql_GetData(sql_handle, 1, &data, &len); + safestrncpy(msg.dest_name, data, NAME_LENGTH); + } + + Sql_FreeResult(sql_handle); + } + // Try to find the Dest Char by Name Sql_EscapeStringLen(sql_handle, esc_name, msg.dest_name, strnlen(msg.dest_name, NAME_LENGTH)); - if ( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id`, `char_id` FROM `%s` WHERE `name` = '%s'", schema_config.char_db, esc_name) ) + if ( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id`, `char_id` FROM `%s` WHERE `name` = '%s'", schema_config.char_db, esc_name) ){ Sql_ShowDebug(sql_handle); - else - if ( SQL_SUCCESS == Sql_NextRow(sql_handle) ) - { - char *data; + }else if ( SQL_SUCCESS == Sql_NextRow(sql_handle) ){ #if PACKETVER < 20150513 uint32 account_id = RFIFOL(fd,4); diff --git a/src/map/script.cpp b/src/map/script.cpp index 31c5a6cc13..dd834b03f8 100644 --- a/src/map/script.cpp +++ b/src/map/script.cpp @@ -23802,6 +23802,260 @@ BUILDIN_FUNC(getequiptradability) { return SCRIPT_CMD_SUCCESS; } +static inline bool mail_sub( struct script_state *st, struct script_data *data, struct map_session_data *sd, int i, const char **out_name, unsigned int *start, unsigned int *end, int32 *id ){ + const char *name; + + // Check if it is a variable + if( !data_isreference(data) ){ + ShowError("buildin_mail: argument %d is not a variable.\n", i ); + return false; + } + + name = reference_getname(data); + + if( is_string_variable(name) ){ + ShowError( "buildin_mail: variable \"%s\" is a string variable.\n", name ); + return false; + } + + // Check if the variable requires a player + if( not_server_variable(*name) && sd == NULL ){ + // If no player is attached + if( !script_rid2sd(sd) ){ + ShowError( "buildin_mail: variable \"%s\" was not a server variable, but no player was attached.\n", name ); + return false; + } + } + + // Try to find the array's source pointer + if( !script_array_src( st, sd, name, reference_getref(data) ) ){ + ShowError( "buildin_mail: variable \"%s\" is not an array.\n" ); + return false; + } + + // Store the name for later usage + *out_name = name; + + // Get the start and end indices of the array + *start = reference_getindex(data); + *end = script_array_highest_key( st, sd, name, reference_getref(data) ); + + // For getting the values we need the id of the array + *id = reference_getid(data); + + return true; +} + +BUILDIN_FUNC(mail){ + const char *sender, *title, *body, *name; + struct mail_message msg; + struct script_data *data; + struct map_session_data *sd = NULL; + unsigned int i, j, k, num_items, start, end; + int32 id; + + memset(&msg, 0, sizeof(struct mail_message)); + + msg.dest_id = script_getnum(st,2); + + sender = script_getstr(st, 3); + + if( strlen(sender) > NAME_LENGTH ){ + ShowError( "buildin_mail: sender name can not be longer than %d characters.\n", NAME_LENGTH ); + return SCRIPT_CMD_FAILURE; + } + + safestrncpy(msg.send_name, sender, NAME_LENGTH); + + title = script_getstr(st, 4); + + if( strlen(title) > MAIL_TITLE_LENGTH ){ + ShowError( "buildin_mail: title can not be longer than %d characters.\n", MAIL_TITLE_LENGTH ); + return SCRIPT_CMD_FAILURE; + } + + safestrncpy(msg.title, title, MAIL_TITLE_LENGTH); + + body = script_getstr(st, 5); + + if( strlen(body) > MAIL_BODY_LENGTH ){ + ShowError( "buildin_mail: body can not be longer than %d characters.\n", MAIL_BODY_LENGTH ); + return SCRIPT_CMD_FAILURE; + } + + safestrncpy(msg.body, body, MAIL_BODY_LENGTH); + + if( script_hasdata(st,6) ){ + int zeny = script_getnum(st, 6); + + if( zeny < 0 ){ + ShowError( "buildin_mail: a negative amount of zeny can not be sent.\n" ); + return SCRIPT_CMD_FAILURE; + }else if( zeny > MAX_ZENY ){ + ShowError( "buildin_mail: amount of zeny %u is exceeding maximum of %u. Capping...\n", zeny, MAX_ZENY ); + zeny = MAX_ZENY; + } + + msg.zeny = zeny; + } + + // Items + num_items = 0; + while( script_hasdata(st,7) ){ + data = script_getdata(st,7); + + if( !mail_sub( st, data, sd, 7, &name, &start, &end, &id ) ){ + return SCRIPT_CMD_FAILURE; + } + + num_items = end - start; + + if( num_items == 0 ){ + ShowWarning( "buildin_mail: array \"%s\" contained no items.\n", name ); + break; + } + + if( num_items > MAIL_MAX_ITEM ){ + ShowWarning( "buildin_mail: array \"%s\" contained %d items, capping to maximum of %d...\n", name, num_items, MAIL_MAX_ITEM ); + num_items = MAIL_MAX_ITEM; + } + + for( i = 0; i < num_items && start < end; i++, start++ ){ + msg.item[i].nameid = (int32)__64BPRTSIZE( get_val2( st, reference_uid( id, start ), reference_getref( data ) ) ); + msg.item[i].identify = 1; + + script_removetop(st, -1, 0); + + if( !itemdb_exists(msg.item[i].nameid) ){ + ShowError( "buildin_mail: invalid item id %hu.\n", msg.item[i].nameid ); + return SCRIPT_CMD_FAILURE; + } + } + + // Amounts + if( !script_hasdata(st,8) ){ + ShowError("buildin_mail: missing item count variable at position %d.\n", 8); + return SCRIPT_CMD_FAILURE; + } + + data = script_getdata(st,8); + + if( !mail_sub( st, data, sd, 8, &name, &start, &end, &id ) ){ + return SCRIPT_CMD_FAILURE; + } + + for( i = 0; i < num_items && start < end; i++, start++ ){ + struct item_data *item = itemdb_exists(msg.item[i].nameid); + + msg.item[i].amount = (int32)__64BPRTSIZE( get_val2( st, reference_uid( id, start ), reference_getref( data ) ) ); + + script_removetop(st, -1, 0); + + if( msg.item[i].amount <= 0 ){ + ShowError( "buildin_mail: amount %d for item %hu is invalid.\n", msg.item[i].amount, msg.item[i].nameid ); + return SCRIPT_CMD_FAILURE; + }else if( itemdb_isstackable2(item) ){ + uint16 max = item->stack.amount > 0 ? item->stack.amount : MAX_AMOUNT; + + if( msg.item[i].amount > max ){ + ShowWarning( "buildin_mail: amount %d for item %hu is exceeding the maximum of %d. Capping...\n", msg.item[i].amount, msg.item[i].nameid, max ); + msg.item[i].amount = max; + } + }else{ + if( msg.item[i].amount > 1 ){ + ShowWarning( "buildin_mail: amount %d is invalid for non-stackable item %hu.\n", msg.item[i].amount, msg.item[i].nameid ); + msg.item[i].amount = 1; + } + } + } + + // Cards + if( !script_hasdata(st,9) ){ + break; + } + + for( i = 0, j = 9; i < MAX_SLOTS && script_hasdata(st,j); i++, j++ ){ + data = script_getdata(st,j); + + if( !mail_sub( st, data, sd, j + 1, &name, &start, &end, &id ) ){ + return SCRIPT_CMD_FAILURE; + } + + for( k = 0; k < num_items && start < end; k++, start++ ){ + msg.item[k].card[i] = (int32)__64BPRTSIZE( get_val2( st, reference_uid( id, start ), reference_getref( data ) ) ); + + script_removetop(st, -1, 0); + + if( msg.item[k].card[i] != 0 && !itemdb_exists(msg.item[k].card[i]) ){ + ShowError( "buildin_mail: invalid card id %hu.\n", msg.item[k].card[i] ); + return SCRIPT_CMD_FAILURE; + } + } + } + + // Random Options + if( !script_hasdata(st,9 + MAX_SLOTS) ){ + break; + } + + for( i = 0, j = 9 + MAX_SLOTS; i < MAX_ITEM_RDM_OPT && script_hasdata(st,j) && script_hasdata(st,j + 1) && script_hasdata(st,j + 2); i++, j++ ){ + // Option IDs + data = script_getdata(st, j); + + if( !mail_sub( st, data, sd, j + 1, &name, &start, &end, &id ) ){ + return SCRIPT_CMD_FAILURE; + } + + for( k = 0; k < num_items && start < end; k++, start++ ){ + msg.item[k].option[i].id = (int32)__64BPRTSIZE( get_val2( st, reference_uid( id, start ), reference_getref( data ) ) ); + + script_removetop(st, -1, 0); + } + + j++; + + // Option values + data = script_getdata(st, j); + + if( !mail_sub( st, data, sd, j + 1, &name, &start, &end, &id ) ){ + return SCRIPT_CMD_FAILURE; + } + + for( k = 0; k < num_items && start < end; k++, start++ ){ + msg.item[k].option[i].value = (int32)__64BPRTSIZE( get_val2( st, reference_uid( id, start ), reference_getref( data ) ) ); + + script_removetop(st, -1, 0); + } + + j++; + + // Option parameters + data = script_getdata(st, j); + + if( !mail_sub( st, data, sd, j + 1, &name, &start, &end, &id ) ){ + return SCRIPT_CMD_FAILURE; + } + + for( k = 0; k < num_items && start < end; k++, start++ ){ + msg.item[k].option[i].param = (int32)__64BPRTSIZE( get_val2( st, reference_uid( id, start ), reference_getref( data ) ) ); + + script_removetop(st, -1, 0); + } + } + + // Break the pseudo scope + break; + } + + msg.status = MAIL_NEW; + msg.type = MAIL_INBOX_NORMAL; + msg.timestamp = time(NULL); + + intif_Mail_send(0, &msg); + + return SCRIPT_CMD_SUCCESS; +} + #include "../custom/script.inc" // declarations that were supposed to be exported from npc_chat.c @@ -24451,6 +24705,7 @@ struct script_function buildin_func[] = { BUILDIN_DEF2(round, "ceil", "i"), BUILDIN_DEF2(round, "floor", "i"), BUILDIN_DEF(getequiptradability, "i?"), + BUILDIN_DEF(mail, "isss*"), #include "../custom/script_def.inc" {NULL,NULL,NULL},