Basic RODEX support (#2186)

Adds basic support for the new mail UI RODEX.

Fixes #1567

Thanks to @RagnarokNova, @Atemo, @aleos89 and everyone else that helped me with this.
Additionally I would like to thank @jezznar and @hazimjauhari90 for their good testing in the pull request.
This commit is contained in:
Lemongrass3110 2017-06-21 00:00:29 +02:00 committed by GitHub
parent 76786e2977
commit 58776da1ac
32 changed files with 1690 additions and 468 deletions

View File

@ -134,6 +134,29 @@ cashshop_show_points: no
// 2 = Yes, when there are unread mails
mail_show_status: 0
// Amount of mails a user can send a day.
// Default: 100
// 0 = Unlimited
mail_daily_count: 100
// Fee for transferring zeny via mail (Note 2)
// NOTE: this rate is hardcoded in the client, you need to diff your client accordingly if you change this value.
// Default: 2(2%)
// 0 = No fee
mail_zeny_fee: 2
// Amount of zeny to pay for each attached item
// NOTE: this fee is hardcoded in the client, you need to diff your client accordingly if you change this value.
// Default: 2500
// 0 = No fee
mail_attachment_price: 2500
// Maximum weight in total that can be attached to a mail
// NOTE: this limit is hardcoded in the client, you need to diff your client accordingly if you change this value.
// Default: 2000
// 0 = No weight limit
mail_attachment_weight: 2000
// Is monster transformation disabled during Guild Wars?
// If set to yes, monster transforming is automatically removed/disabled when enterting castles during WoE times
mon_trans_disable_in_gvg: no

View File

@ -261,4 +261,19 @@ default_map_y: 191
// Default: 14
clan_remove_inactive_days: 14
//===================================
// RODEX
//===================================
// After how many days should mails be returned to their sender?
// 0: never return them
// X: return them after X days
// Default: 14
mail_return_days: 14
// How many days after a mail was returned to it's sender should it be deleted completely?
// 0: never delete them
// X: delete them X days after they were returned
// Default: 14
mail_delete_days: 14
import: conf/import/char_conf.txt

View File

@ -108,6 +108,7 @@ party_db: party
pet_db: pet
friend_db: friends
mail_db: mail
mail_attachment_db: mail_attachments
auction_db: auction
quest_db: quest
homunculus_db: homunculus

View File

@ -2497,31 +2497,31 @@ packet_keys: 0x62C86D09,0x75944F17,0x112C133D // [YomRawr]
// RODEX Mail system
0x09E7,3 // ZC_NOTIFY_UNREADMAIL
0x09E8,11,dull,0 // CZ_OPEN_MAILBOX
0x09E8,11,mailrefresh,2:3 // CZ_OPEN_MAILBOX
0x09E9,2,dull,0 // CZ_CLOSE_MAILBOX
0x09EA,11,dull,0 // CZ_REQ_READ_MAIL
0x09EA,11,mailread,2:3 // CZ_REQ_READ_MAIL
0x09EB,-1 // ZC_ACK_READ_MAIL
0x09EC,-1,dull,0 // CZ_REQ_WRITE_MAIL
0x09EC,-1,mailsend,2:4:28:52:60:62:64 // CZ_REQ_WRITE_MAIL
0x09ED,3 // ZC_ACK_WRITE_MAIL
0x09EE,11,dull,0 // CZ_REQ_NEXT_MAIL_LIST
0x09EF,11,dull,0 // CZ_REQ_REFRESH_MAIL_LIST
0x09EE,11,mailrefresh,2:3 // CZ_REQ_NEXT_MAIL_LIST
0x09EF,11,mailrefresh,2:3 // CZ_REQ_REFRESH_MAIL_LIST
0x09F0,-1 // ZC_ACK_MAIL_LIST
0x09F1,11,dull,0 // CZ_REQ_ZENY_FROM_MAIL
0x09F1,11,mailgetattach,0 // CZ_REQ_ZENY_FROM_MAIL
0x09F2,12 // ZC_ACK_ZENY_FROM_MAIL
0x09F3,11,dull,0 // CZ_REQ_ITEM_FROM_MAIL
0x09F3,11,mailgetattach,0 // CZ_REQ_ITEM_FROM_MAIL
0x09F4,12 // ZC_ACK_ITEM_FROM_MAIL
0x09F5,11,dull,0 // CZ_REQ_DELETE_MAIL
0x09F5,11,maildelete,0 // CZ_REQ_DELETE_MAIL
0x09F6,11 // ZC_ACK_DELETE_MAIL
0x0A03,2,dull,0 // CZ_REQ_CANCEL_WRITE_MAIL
0x0A04,6,dull,0 // CZ_REQ_ADD_ITEM_TO_MAIL
0x0A03,2,mailcancel,0 // CZ_REQ_CANCEL_WRITE_MAIL
0x0A04,6,mailsetattach,2:4 // CZ_REQ_ADD_ITEM_TO_MAIL
0x0A05,53 // ZC_ACK_ADD_ITEM_TO_MAIL
0x0A06,6,dull,0 // CZ_REQ_REMOVE_ITEM_MAIL
0x0A06,6,mailwinopen,2:4 // CZ_REQ_REMOVE_ITEM_MAIL
0x0A07,9 // ZC_ACK_REMOVE_ITEM_MAIL
0x0A08,26,dull,0 // CZ_REQ_OPEN_WRITE_MAIL
0x0A08,26,mailbegin,0 // CZ_REQ_OPEN_WRITE_MAIL
0x0A12,27 // ZC_ACK_OPEN_WRITE_MAIL
0x0A32,2 // ZC_OPEN_RODEX_THROUGH_NPC_ONLY
0x0A13,26,dull,0 // CZ_CHECK_RECEIVE_CHARACTER_NAME
0x0A13,26,mailreceiver,2 // CZ_CHECK_RECEIVE_CHARACTER_NAME
0x0A14,10 // ZC_CHECK_RECEIVE_CHARACTER_NAME
0x0A32,2 // ZC_OPEN_RODEX_THROUGH_NPC_ONLY
// New EquipPackets Support
0x0A09,45 // ZC_ADD_EXCHANGE_ITEM3
@ -2657,6 +2657,15 @@ packet_keys: 0x4C17382A,0x7ED174C9,0x29961E4F // [Winnie]
0x088D,5,hommenu,2:4
0x0940,36,storagepassword,2:4:20
// 2016-03-02bRagexe
0x0A51,34
// 2016-03-30aRagexe
0x0A6E,-1,mailsend,2:4:28:52:60:62:64:68 // CZ_REQ_WRITE_MAIL2
// 2016-06-01aRagexe
0x0A7D,-1
// 2017-05-24aRagexeRE
0x0A43,85
0x0A44,-1

View File

@ -1006,13 +1006,14 @@ Currently the max packet size is 0xFFFF (see 'WFIFOSET()' in 'src/common/socket.
0x3048
Type: ZI
Structure: <cmd>.W <cid>.L <flag>.B
index: 0,2,6
len: 7
Structure: <cmd>.W <cid>.L <flag>.B <mail_type>.B
index: 0,2,6,7
len: 8
parameter:
- cmd : packet identification (0x3048)
- cid
- flag
- mail_type
desc:
- Inbox request
@ -1029,13 +1030,14 @@ Currently the max packet size is 0xFFFF (see 'WFIFOSET()' in 'src/common/socket.
0x304a
Type: ZI
Structure: <cmd>.W <cid>.L <mail_id>.L
index: 0,2,6
len: 10
Structure: <cmd>.W <cid>.L <mail_id>.L <attachment_type>.B
index: 0,2,6,10
len: 11
parameter:
- cmd : packet identification (0x304a)
- cid
- mail_id
- attachment_type
desc:
- Mail get attachment
@ -1076,6 +1078,18 @@ Currently the max packet size is 0xFFFF (see 'WFIFOSET()' in 'src/common/socket.
desc:
- Mail send
0x304e
Type: ZI
Structure: <cmd>.W <cid>.L <name>.24B
index: 0,2,6
len: 30
parameter:
- cmd : packet identification (0x304e)
- cid
- name
desc:
- Checks if a character with the given name exists.
0x3050
Type: ZI
Structure: <cmd>.W <len>.W <cid>.L <type>.W <price>.L <page>.W <searchtext>.?B
@ -1966,14 +1980,15 @@ Currently the max packet size is 0xFFFF (see 'WFIFOSET()' in 'src/common/socket.
0x3848
Type: IZ
Structure: <cmd>.W <size>.W <char_id>.L <flag>.B <md>.?B
index: 0,2,4,8,9
len: variable: 9+md
Structure: <cmd>.W <size>.W <char_id>.L <flag>.B <mail_type>.B <md>.?B
index: 0,2,4,8,9,10
len: variable: 10+md
parameter:
- cmd : packet identification (0x3848)
- size
- char_id
- flag
- mail_type
- md : Mail
desc:
- A player request for mail inbox
@ -2044,6 +2059,21 @@ Currently the max packet size is 0xFFFF (see 'WFIFOSET()' in 'src/common/socket.
desc:
- Mail sent status (to player if the sender is player and online)
0x384e
Type: IZ
Structure: <cmd>.W <cid_sender>.L <cid_receiver>.L <class>.W <level>.W <name>.24B
index: 0,2,6,10,12,14
len: 38
parameter:
- cmd : packet identification (0x384e)
- cid_sender
- cid_receiver
- class
- level
- name
desc:
- Mail receiver's character data(character id, job, level and name)
0x3850
Type: IZ
Structure: <cmd>.W <size>.W <char_id>.L <count>.W <pages>.W <auction_data>.?B

View File

@ -6097,6 +6097,12 @@ while hidden then revealing.... you can wonder around =P
---------------------------------------
*unloadnpc "<NPC object name>";
This command will fully unload a NPC object and all of it's duplicates.
---------------------------------------
*doevent "<NPC object name>::<event label>";
This command will start a new execution thread in a specified NPC object at the

View File

@ -137,3 +137,14 @@ rachel,122,146,0 duplicate(MailBox) Post Box#ra 888
// Veins
//============================================================
veins,218,123,0 duplicate(MailBox) Post Box#ve 888
// RODEX makes these NPCs useless
- script RodexMailBoxInit -1,{
end;
OnInit:
if( PACKETVER >= 20150513 ){
unloadnpc "MailBox";
}
end;
}

View File

@ -716,35 +716,46 @@ CREATE TABLE IF NOT EXISTS `mail` (
`time` int(11) unsigned NOT NULL default '0',
`status` tinyint(2) NOT NULL default '0',
`zeny` int(11) unsigned NOT NULL default '0',
`nameid` smallint(5) unsigned NOT NULL default '0',
`amount` int(11) unsigned NOT NULL default '0',
`refine` tinyint(3) unsigned NOT NULL default '0',
`attribute` tinyint(4) unsigned NOT NULL default '0',
`identify` smallint(6) NOT NULL default '0',
`card0` smallint(5) unsigned NOT NULL default '0',
`card1` smallint(5) unsigned NOT NULL default '0',
`card2` smallint(5) unsigned NOT NULL default '0',
`card3` smallint(5) unsigned NOT NULL default '0',
`option_id0` smallint(5) unsigned NOT NULL default '0',
`option_val0` smallint(5) unsigned NOT NULL default '0',
`option_parm0` tinyint(3) unsigned NOT NULL default '0',
`option_id1` smallint(5) unsigned NOT NULL default '0',
`option_val1` smallint(5) unsigned NOT NULL default '0',
`option_parm1` tinyint(3) unsigned NOT NULL default '0',
`option_id2` smallint(5) unsigned NOT NULL default '0',
`option_val2` smallint(5) unsigned NOT NULL default '0',
`option_parm2` tinyint(3) unsigned NOT NULL default '0',
`option_id3` smallint(5) unsigned NOT NULL default '0',
`option_val3` smallint(5) unsigned NOT NULL default '0',
`option_parm3` tinyint(3) unsigned NOT NULL default '0',
`option_id4` smallint(5) unsigned NOT NULL default '0',
`option_val4` smallint(5) unsigned NOT NULL default '0',
`option_parm4` tinyint(3) unsigned NOT NULL default '0',
`unique_id` bigint(20) unsigned NOT NULL default '0',
`bound` tinyint(1) unsigned NOT NULL default '0',
`type` smallint(5) NOT NULL default '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM;
-- ----------------------------
-- Table structure for `mail_attachments`
-- ----------------------------
CREATE TABLE IF NOT EXISTS `mail_attachments` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`index` smallint(5) unsigned NOT NULL DEFAULT '0',
`nameid` smallint(5) unsigned NOT NULL DEFAULT '0',
`amount` int(11) unsigned NOT NULL DEFAULT '0',
`refine` tinyint(3) unsigned NOT NULL DEFAULT '0',
`attribute` tinyint(4) unsigned NOT NULL DEFAULT '0',
`identify` smallint(6) NOT NULL DEFAULT '0',
`card0` smallint(5) unsigned NOT NULL DEFAULT '0',
`card1` smallint(5) unsigned NOT NULL DEFAULT '0',
`card2` smallint(5) unsigned NOT NULL DEFAULT '0',
`card3` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_id0` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_val0` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_parm0` tinyint(3) unsigned NOT NULL DEFAULT '0',
`option_id1` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_val1` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_parm1` tinyint(3) unsigned NOT NULL DEFAULT '0',
`option_id2` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_val2` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_parm2` tinyint(3) unsigned NOT NULL DEFAULT '0',
`option_id3` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_val3` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_parm3` tinyint(3) unsigned NOT NULL DEFAULT '0',
`option_id4` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_val4` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_parm4` tinyint(3) unsigned NOT NULL DEFAULT '0',
`unique_id` bigint(20) unsigned NOT NULL DEFAULT '0',
`bound` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`,`index`)
) ENGINE=MyISAM;
--
-- Table structure for table `mapreg`
--

View File

@ -0,0 +1,76 @@
-- ----------------------------
-- Table structure for `mail_attachments`
-- ----------------------------
CREATE TABLE IF NOT EXISTS `mail_attachments` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`index` smallint(5) unsigned NOT NULL DEFAULT '0',
`nameid` smallint(5) unsigned NOT NULL DEFAULT '0',
`amount` int(11) unsigned NOT NULL DEFAULT '0',
`refine` tinyint(3) unsigned NOT NULL DEFAULT '0',
`attribute` tinyint(4) unsigned NOT NULL DEFAULT '0',
`identify` smallint(6) NOT NULL DEFAULT '0',
`card0` smallint(5) unsigned NOT NULL DEFAULT '0',
`card1` smallint(5) unsigned NOT NULL DEFAULT '0',
`card2` smallint(5) unsigned NOT NULL DEFAULT '0',
`card3` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_id0` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_val0` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_parm0` tinyint(3) unsigned NOT NULL DEFAULT '0',
`option_id1` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_val1` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_parm1` tinyint(3) unsigned NOT NULL DEFAULT '0',
`option_id2` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_val2` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_parm2` tinyint(3) unsigned NOT NULL DEFAULT '0',
`option_id3` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_val3` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_parm3` tinyint(3) unsigned NOT NULL DEFAULT '0',
`option_id4` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_val4` smallint(5) unsigned NOT NULL DEFAULT '0',
`option_parm4` tinyint(3) unsigned NOT NULL DEFAULT '0',
`unique_id` bigint(20) unsigned NOT NULL DEFAULT '0',
`bound` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`,`index`)
) ENGINE=MyISAM;
insert into `mail_attachments`
(`id`,`index`,`nameid`,`amount`,`refine`,`attribute`,`identify`,`card0`,`card1`,`card2`,`card3`,`option_id0`,`option_val0`,`option_parm0`,`option_id1`,`option_val1`,`option_parm1`,`option_id2`,`option_val2`,`option_parm2`,`option_id3`,`option_val3`,`option_parm3`,`option_id4`,`option_val4`,`option_parm4`,`unique_id`,`bound`)
select
`id`,'0',`nameid`,`amount`,`refine`,`attribute`,`identify`,`card0`,`card1`,`card2`,`card3`,`option_id0`,`option_val0`,`option_parm0`,`option_id1`,`option_val1`,`option_parm1`,`option_id2`,`option_val2`,`option_parm2`,`option_id3`,`option_val3`,`option_parm3`,`option_id4`,`option_val4`,`option_parm4`,`unique_id`,`bound`
from `mail`
where `nameid` <> 0;
alter table `mail`
drop column `nameid`,
drop column `amount`,
drop column `refine`,
drop column `attribute`,
drop column `identify`,
drop column `card0`,
drop column `card1`,
drop column `card2`,
drop column `card3`,
drop column `option_id0`,
drop column `option_val0`,
drop column `option_parm0`,
drop column `option_id1`,
drop column `option_val1`,
drop column `option_parm1`,
drop column `option_id2`,
drop column `option_val2`,
drop column `option_parm2`,
drop column `option_id3`,
drop column `option_val3`,
drop column `option_parm3`,
drop column `option_id4`,
drop column `option_val4`,
drop column `option_parm4`,
drop column `unique_id`,
drop column `bound`;
alter table `mail`
modify `message` varchar(500) NOT NULL default '';
ALTER TABLE `mail`
ADD COLUMN `type` smallint(5) NOT NULL default '0';

View File

@ -20,6 +20,7 @@
#include "../common/cli.h"
#include "int_guild.h"
#include "int_homun.h"
#include "int_mail.h"
#include "int_mercenary.h"
#include "int_elemental.h"
#include "int_party.h"
@ -1649,6 +1650,10 @@ int char_delete_char_sql(uint32 char_id){
if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id`='%d'", schema_config.skill_db, char_id) )
Sql_ShowDebug(sql_handle);
/* delete mail attachments (only received) */
if (SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `id` IN ( SELECT `id` FROM `%s` WHERE `dest_id`='%d' )", schema_config.mail_attachment_db, schema_config.mail_db, char_id))
Sql_ShowDebug(sql_handle);
/* delete mails (only received) */
if (SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `dest_id`='%d'", schema_config.mail_db, char_id))
Sql_ShowDebug(sql_handle);
@ -2228,7 +2233,7 @@ bool char_checkdb(void){
schema_config.auction_db, schema_config.quest_db, schema_config.homunculus_db, schema_config.skill_homunculus_db,
schema_config.mercenary_db, schema_config.mercenary_owner_db,
schema_config.elemental_db, schema_config.ragsrvinfo_db, schema_config.skillcooldown_db, schema_config.bonus_script_db,
schema_config.clan_table, schema_config.clan_alliance_table
schema_config.clan_table, schema_config.clan_alliance_table, schema_config.mail_attachment_db
};
ShowInfo("Start checking DB integrity\n");
for (i=0; i<ARRAYLENGTH(sqltable); i++){ //check if they all exist and we can acces them in sql-server
@ -2365,12 +2370,19 @@ bool char_checkdb(void){
}
//checking mail_db
if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `id`,`send_name`,`send_id`,`dest_name`,`dest_id`,"
"`title`,`message`,`time`,`status`,`zeny`,`nameid`,`amount`,`refine`,`attribute`,`identify`,"
"`card0`,`card1`,`card2`,`card3`,`option_id0`,`option_val0`,`option_parm0`,`option_id1`,`option_val1`,`option_parm1`,`option_id2`,`option_val2`,`option_parm2`,`option_id3`,`option_val3`,`option_parm3`,`option_id4`,`option_val4`,`option_parm4`,`unique_id`, `bound`"
"`title`,`message`,`time`,`status`,`zeny`,`type`"
" FROM `%s` LIMIT 1;", schema_config.mail_db) ){
Sql_ShowDebug(sql_handle);
return false;
}
//checking mail_attachment_db
if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `id`,`index`,`nameid`,`amount`,`refine`,`attribute`,`identify`,"
"`card0`,`card1`,`card2`,`card3`,`option_id0`,`option_val0`,`option_parm0`,`option_id1`,`option_val1`,`option_parm1`,`option_id2`,`option_val2`,`option_parm2`,`option_id3`,`option_val3`,`option_parm3`,`option_id4`,`option_val4`,`option_parm4`,`unique_id`, `bound`"
" FROM `%s` LIMIT 1;", schema_config.mail_attachment_db) ){
Sql_ShowDebug(sql_handle);
return false;
}
//checking auction_db
if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `auction_id`,`seller_id`,`seller_name`,`buyer_id`,`buyer_name`,"
"`price`,`buynow`,`hours`,`timestamp`,`nameid`,`item_name`,`type`,`refine`,`attribute`,`card0`,`card1`,"
@ -2522,6 +2534,8 @@ void char_sql_config_read(const char* cfgName) {
safestrncpy(schema_config.pet_db, w2, sizeof(schema_config.pet_db));
else if(!strcmpi(w1,"mail_db"))
safestrncpy(schema_config.mail_db, w2, sizeof(schema_config.mail_db));
else if (!strcmpi(w1, "mail_attachment_db"))
safestrncpy(schema_config.mail_attachment_db, w2, sizeof(schema_config.mail_attachment_db));
else if(!strcmpi(w1,"auction_db"))
safestrncpy(schema_config.auction_db, w2, sizeof(schema_config.auction_db));
else if(!strcmpi(w1,"friend_db"))
@ -2699,6 +2713,8 @@ void char_set_defaults(){
charserv_config.default_map_y = 191;
charserv_config.clan_remove_inactive_days = 14;
charserv_config.mail_return_days = 14;
charserv_config.mail_delete_days = 14;
}
/**
@ -2973,6 +2989,10 @@ bool char_config_read(const char* cfgName, bool normal){
charserv_config.default_map_y = atoi(w2);
} else if (strcmpi(w1, "clan_remove_inactive_days") == 0) {
charserv_config.clan_remove_inactive_days = atoi(w2);
} else if (strcmpi(w1, "mail_return_days") == 0) {
charserv_config.mail_return_days = atoi(w2);
} else if (strcmpi(w1, "mail_delete_days") == 0) {
charserv_config.mail_delete_days = atoi(w2);
} else if (strcmpi(w1, "import") == 0) {
char_config_read(w2, normal);
}
@ -3147,6 +3167,14 @@ int do_init(int argc, char **argv)
add_timer_func_list(char_clan_member_cleanup, "clan_member_cleanup");
add_timer_interval(gettick() + 1000, char_clan_member_cleanup, 0, 0, 60 * 60 * 1000); // every 60 minutes
// periodically check if mails need to be returned to their sender
add_timer_func_list(mail_return_timer, "mail_return_timer");
add_timer_interval(gettick() + 1000, mail_return_timer, 0, 0, 5 * 60 * 1000); // every 5 minutes
// periodically check if mails need to be deleted completely
add_timer_func_list(mail_delete_timer, "mail_delete_timer");
add_timer_interval(gettick() + 1000, mail_delete_timer, 0, 0, 5 * 60 * 1000); // every 5 minutes
//check db tables
if(charserv_config.char_check_db && char_checkdb() == 0){
ShowFatalError("char : A tables is missing in sql-server, please fix it, see (sql-files main.sql for structure) \n");

View File

@ -58,6 +58,7 @@ struct Schema_Config {
char party_db[DB_NAME_LEN];
char pet_db[DB_NAME_LEN];
char mail_db[DB_NAME_LEN]; // MAIL SYSTEM
char mail_attachment_db[DB_NAME_LEN];
char auction_db[DB_NAME_LEN]; // Auctions System
char friend_db[DB_NAME_LEN];
char hotkey_db[DB_NAME_LEN];
@ -167,6 +168,8 @@ struct CharServ_Config {
unsigned short default_map_y;
int clan_remove_inactive_days;
int mail_return_days;
int mail_delete_days;
};
extern struct CharServ_Config charserv_config;

View File

@ -154,12 +154,12 @@ static int auction_end_timer(int tid, unsigned int tick, int id, intptr_t data)
{
if( auction->buyer_id )
{
mail_sendmail(0, msg_txt(200), auction->buyer_id, auction->buyer_name, msg_txt(201), msg_txt(202), 0, &auction->item);
mail_sendmail(0, msg_txt(200), auction->buyer_id, auction->buyer_name, msg_txt(201), msg_txt(202), 0, &auction->item, 1);
mapif_Auction_message(auction->buyer_id, 6); // You have won the auction
mail_sendmail(0, msg_txt(200), auction->seller_id, auction->seller_name, msg_txt(201), msg_txt(203), auction->price, NULL);
mail_sendmail(0, msg_txt(200), auction->seller_id, auction->seller_name, msg_txt(201), msg_txt(203), auction->price, NULL, 0);
}
else
mail_sendmail(0, msg_txt(200), auction->seller_id, auction->seller_name, msg_txt(201), msg_txt(204), 0, &auction->item);
mail_sendmail(0, msg_txt(200), auction->seller_id, auction->seller_name, msg_txt(201), msg_txt(204), 0, &auction->item, 1);
ShowInfo("Auction End: id %u.\n", auction->auction_id);
@ -376,7 +376,7 @@ static void mapif_parse_Auction_cancel(int fd)
return;
}
mail_sendmail(0, msg_txt(200), auction->seller_id, auction->seller_name, msg_txt(201), msg_txt(205), 0, &auction->item);
mail_sendmail(0, msg_txt(200), auction->seller_id, auction->seller_name, msg_txt(201), msg_txt(205), 0, &auction->item, 1);
auction_delete(auction);
mapif_Auction_cancel(fd, char_id, 0); // The auction has been canceled
@ -415,9 +415,9 @@ static void mapif_parse_Auction_close(int fd)
}
// Send Money to Seller
mail_sendmail(0, msg_txt(200), auction->seller_id, auction->seller_name, msg_txt(201), msg_txt(206), auction->price, NULL);
mail_sendmail(0, msg_txt(200), auction->seller_id, auction->seller_name, msg_txt(201), msg_txt(206), auction->price, NULL, 0);
// Send Item to Buyer
mail_sendmail(0, msg_txt(200), auction->buyer_id, auction->buyer_name, msg_txt(201), msg_txt(207), 0, &auction->item);
mail_sendmail(0, msg_txt(200), auction->buyer_id, auction->buyer_name, msg_txt(201), msg_txt(207), 0, &auction->item, 1);
mapif_Auction_message(auction->buyer_id, 6); // You have won the auction
auction_delete(auction);
@ -456,11 +456,11 @@ static void mapif_parse_Auction_bid(int fd)
{ // Send Money back to the previous Buyer
if( auction->buyer_id != char_id )
{
mail_sendmail(0, msg_txt(200), auction->buyer_id, auction->buyer_name, msg_txt(201), msg_txt(208), auction->price, NULL);
mail_sendmail(0, msg_txt(200), auction->buyer_id, auction->buyer_name, msg_txt(201), msg_txt(208), auction->price, NULL, 0);
mapif_Auction_message(auction->buyer_id, 7); // You have failed to win the auction
}
else
mail_sendmail(0, msg_txt(200), auction->buyer_id, auction->buyer_name, msg_txt(201), msg_txt(209), auction->price, NULL);
mail_sendmail(0, msg_txt(200), auction->buyer_id, auction->buyer_name, msg_txt(201), msg_txt(209), auction->price, NULL, 0);
}
auction->buyer_id = char_id;
@ -471,9 +471,9 @@ static void mapif_parse_Auction_bid(int fd)
{ // Automatic won the auction
mapif_Auction_bid(fd, char_id, bid - auction->buynow, 1); // You have successfully bid in the auction
mail_sendmail(0, msg_txt(200), auction->buyer_id, auction->buyer_name, msg_txt(201), msg_txt(210), 0, &auction->item);
mail_sendmail(0, msg_txt(200), auction->buyer_id, auction->buyer_name, msg_txt(201), msg_txt(210), 0, &auction->item, 1);
mapif_Auction_message(char_id, 6); // You have won the auction
mail_sendmail(0, msg_txt(200), auction->seller_id, auction->seller_name, msg_txt(201), msg_txt(211), auction->buynow, NULL);
mail_sendmail(0, msg_txt(200), auction->seller_id, auction->seller_name, msg_txt(201), msg_txt(211), auction->buynow, NULL, 0);
auction_delete(auction);
return;

View File

@ -12,87 +12,47 @@
#include <stdlib.h>
static bool mail_loadmessage(int mail_id, struct mail_message* msg);
static void mapif_Mail_return(int fd, uint32 char_id, int mail_id);
static void mapif_Mail_delete(int fd, uint32 char_id, int mail_id, bool deleted);
static int mail_fromsql(uint32 char_id, struct mail_data* md)
{
int i, j;
struct mail_message *msg;
int i;
char *data;
StringBuf buf;
memset(md, 0, sizeof(struct mail_data));
md->amount = 0;
md->full = false;
StringBuf_Init(&buf);
StringBuf_AppendStr(&buf, "SELECT `id`,`send_name`,`send_id`,`dest_name`,`dest_id`,`title`,`message`,`time`,`status`,"
"`zeny`,`amount`,`nameid`,`refine`,`attribute`,`identify`,`unique_id`,`bound`");
for (i = 0; i < MAX_SLOTS; i++)
StringBuf_Printf(&buf, ",`card%d`", i);
for (i = 0; i < MAX_ITEM_RDM_OPT; ++i) {
StringBuf_Printf(&buf, ", `option_id%d`", i);
StringBuf_Printf(&buf, ", `option_val%d`", i);
StringBuf_Printf(&buf, ", `option_parm%d`", i);
}
// I keep the `status` < 3 just in case someone forget to apply the sqlfix
StringBuf_Printf(&buf, " FROM `%s` WHERE `dest_id`='%d' AND `status` < 3 ORDER BY `id` LIMIT %d",
schema_config.mail_db, char_id, MAIL_MAX_INBOX + 1);
if( SQL_ERROR == Sql_Query(sql_handle, StringBuf_Value(&buf)) )
// First we prefill the msg ids
if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `id` FROM `%s` WHERE `dest_id`='%d' AND `status` < 3 ORDER BY `id` LIMIT %d", schema_config.mail_db, char_id, MAIL_MAX_INBOX + 1) ){
Sql_ShowDebug(sql_handle);
StringBuf_Destroy(&buf);
for (i = 0; i < MAIL_MAX_INBOX && SQL_SUCCESS == Sql_NextRow(sql_handle); ++i )
{
struct item *item;
msg = &md->msg[i];
Sql_GetData(sql_handle, 0, &data, NULL); msg->id = atoi(data);
Sql_GetData(sql_handle, 1, &data, NULL); safestrncpy(msg->send_name, data, NAME_LENGTH);
Sql_GetData(sql_handle, 2, &data, NULL); msg->send_id = strtoul(data, NULL, 10);
Sql_GetData(sql_handle, 3, &data, NULL); safestrncpy(msg->dest_name, data, NAME_LENGTH);
Sql_GetData(sql_handle, 4, &data, NULL); msg->dest_id = strtoul(data, NULL, 10);
Sql_GetData(sql_handle, 5, &data, NULL); safestrncpy(msg->title, data, MAIL_TITLE_LENGTH);
Sql_GetData(sql_handle, 6, &data, NULL); safestrncpy(msg->body, data, MAIL_BODY_LENGTH);
Sql_GetData(sql_handle, 7, &data, NULL); msg->timestamp = atoi(data); //strtoull ?
Sql_GetData(sql_handle, 8, &data, NULL); msg->status = (mail_status)atoi(data);
Sql_GetData(sql_handle, 9, &data, NULL); msg->zeny = strtoul(data, NULL, 10);
item = &msg->item;
Sql_GetData(sql_handle,10, &data, NULL); item->amount = (short)atoi(data);
Sql_GetData(sql_handle,11, &data, NULL); item->nameid = atoi(data);
Sql_GetData(sql_handle,12, &data, NULL); item->refine = atoi(data);
Sql_GetData(sql_handle,13, &data, NULL); item->attribute = atoi(data);
Sql_GetData(sql_handle,14, &data, NULL); item->identify = atoi(data);
Sql_GetData(sql_handle,15, &data, NULL); item->unique_id = strtoull(data, NULL, 10);
Sql_GetData(sql_handle,16, &data, NULL); item->bound = atoi(data);
item->expire_time = 0;
for (j = 0; j < MAX_SLOTS; j++)
{
Sql_GetData(sql_handle, 17 + j, &data, NULL);
item->card[j] = atoi(data);
}
for (j = 0; j < MAX_ITEM_RDM_OPT; j++) {
Sql_GetData(sql_handle, 17 + MAX_SLOTS + j * 3, &data, NULL);
item->option[j].id = atoi(data);
Sql_GetData(sql_handle, 18 + MAX_SLOTS + j * 3, &data, NULL);
item->option[j].value = atoi(data);
Sql_GetData(sql_handle, 19 + MAX_SLOTS + j * 3, &data, NULL);
item->option[j].param = atoi(data);
}
return 0;
}
md->full = ( Sql_NumRows(sql_handle) > MAIL_MAX_INBOX );
md->full = (Sql_NumRows(sql_handle) > MAIL_MAX_INBOX);
for( i = 0; i < MAIL_MAX_INBOX && SQL_SUCCESS == Sql_NextRow(sql_handle); i++ ){
Sql_GetData(sql_handle, 0, &data, NULL); md->msg[i].id = atoi(data);
}
md->amount = i;
Sql_FreeResult(sql_handle);
// Now we load them
for( i = 0; i < md->amount; i++ ){
if( !mail_loadmessage( md->msg[i].id, &md->msg[i] ) ){
return 0;
}
}
md->unchecked = 0;
md->unread = 0;
for (i = 0; i < md->amount; i++)
{
msg = &md->msg[i];
struct mail_message *msg = &md->msg[i];
if( msg->status == MAIL_NEW )
{
if ( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `status` = '%d' WHERE `id` = '%d'", schema_config.mail_db, MAIL_UNREAD, msg->id) )
@ -115,27 +75,13 @@ int mail_savemessage(struct mail_message* msg)
{
StringBuf buf;
SqlStmt* stmt;
int j;
int i, j;
bool found = false;
// build message save query
StringBuf_Init(&buf);
StringBuf_Printf(&buf, "INSERT INTO `%s` (`send_name`, `send_id`, `dest_name`, `dest_id`, `title`, `message`, `time`, `status`, `zeny`, `amount`, `nameid`, `refine`, `attribute`, `identify`, `unique_id`, `bound`", schema_config.mail_db);
for (j = 0; j < MAX_SLOTS; j++)
StringBuf_Printf(&buf, ", `card%d`", j);
for (j = 0; j < MAX_ITEM_RDM_OPT; ++j) {
StringBuf_Printf(&buf, ", `option_id%d`", j);
StringBuf_Printf(&buf, ", `option_val%d`", j);
StringBuf_Printf(&buf, ", `option_parm%d`", j);
}
StringBuf_Printf(&buf, ") VALUES (?, '%d', ?, '%d', ?, ?, '%lu', '%d', '%d', '%d', '%hu', '%d', '%d', '%d', '%"PRIu64"', '%d'",
msg->send_id, msg->dest_id, (unsigned long)msg->timestamp, msg->status, msg->zeny, msg->item.amount, msg->item.nameid, msg->item.refine, msg->item.attribute, msg->item.identify, msg->item.unique_id, msg->item.bound);
for (j = 0; j < MAX_SLOTS; j++)
StringBuf_Printf(&buf, ", '%hu'", msg->item.card[j]);
for (j = 0; j < MAX_ITEM_RDM_OPT; ++j) {
StringBuf_Printf(&buf, ", '%d'", msg->item.option[j].id);
StringBuf_Printf(&buf, ", '%d'", msg->item.option[j].value);
StringBuf_Printf(&buf, ", '%d'", msg->item.option[j].param);
}
StringBuf_Printf(&buf, "INSERT INTO `%s` (`send_name`, `send_id`, `dest_name`, `dest_id`, `title`, `message`, `time`, `status`, `zeny`,`type`", schema_config.mail_db);
StringBuf_Printf(&buf, ") VALUES (?, '%d', ?, '%d', ?, ?, '%lu', '%d', '%d', '%d'", msg->send_id, msg->dest_id, (unsigned long)msg->timestamp, msg->status, msg->zeny, msg->type);
StringBuf_AppendStr(&buf, ")");
// prepare and execute query
@ -148,11 +94,50 @@ int mail_savemessage(struct mail_message* msg)
|| SQL_SUCCESS != SqlStmt_Execute(stmt) )
{
SqlStmt_ShowDebug(stmt);
msg->id = 0;
StringBuf_Destroy(&buf);
return msg->id = 0;
} else
msg->id = (int)SqlStmt_LastInsertId(stmt);
SqlStmt_Free(stmt);
StringBuf_Clear(&buf);
StringBuf_Printf(&buf,"INSERT INTO `%s` (`id`, `index`, `amount`, `nameid`, `refine`, `attribute`, `identify`, `unique_id`, `bound`", schema_config.mail_attachment_db);
for (j = 0; j < MAX_SLOTS; j++)
StringBuf_Printf(&buf, ", `card%d`", j);
for (j = 0; j < MAX_ITEM_RDM_OPT; ++j) {
StringBuf_Printf(&buf, ", `option_id%d`", j);
StringBuf_Printf(&buf, ", `option_val%d`", j);
StringBuf_Printf(&buf, ", `option_parm%d`", j);
}
StringBuf_AppendStr(&buf, ") VALUES ");
for( i = 0; i < MAIL_MAX_ITEM; i++ ){
// skip empty and already matched entries
if( msg->item[i].nameid == 0 )
continue;
if( found ){
StringBuf_AppendStr(&buf, ",");
}else{
found = true;
}
StringBuf_Printf(&buf, "('%"PRIu64"', '%hu', '%d', '%hu', '%d', '%d', '%d', '%"PRIu64"', '%d'", (uint64)msg->id, i, msg->item[i].amount, msg->item[i].nameid, msg->item[i].refine, msg->item[i].attribute, msg->item[i].identify, msg->item[i].unique_id, msg->item[i].bound);
for (j = 0; j < MAX_SLOTS; j++)
StringBuf_Printf(&buf, ", '%hu'", msg->item[i].card[j]);
for (j = 0; j < MAX_ITEM_RDM_OPT; ++j) {
StringBuf_Printf(&buf, ", '%d'", msg->item[i].option[j].id);
StringBuf_Printf(&buf, ", '%d'", msg->item[i].option[j].value);
StringBuf_Printf(&buf, ", '%d'", msg->item[i].option[j].param);
}
StringBuf_AppendStr(&buf, ")");
}
if( found && SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf)) ){
Sql_ShowDebug(sql_handle);
}
StringBuf_Destroy(&buf);
return msg->id;
@ -162,33 +147,16 @@ int mail_savemessage(struct mail_message* msg)
/// Returns true if the operation succeeds (or false if it fails).
static bool mail_loadmessage(int mail_id, struct mail_message* msg)
{
int j;
int i, j;
StringBuf buf;
char* data;
StringBuf_Init(&buf);
StringBuf_AppendStr(&buf, "SELECT `id`,`send_name`,`send_id`,`dest_name`,`dest_id`,`title`,`message`,`time`,`status`,"
"`zeny`,`amount`,`nameid`,`refine`,`attribute`,`identify`,`unique_id`,`bound`");
for( j = 0; j < MAX_SLOTS; j++ )
StringBuf_Printf(&buf, ",`card%d`", j);
for (j = 0; j < MAX_ITEM_RDM_OPT; ++j) {
StringBuf_Printf(&buf, ", `option_id%d`", j);
StringBuf_Printf(&buf, ", `option_val%d`", j);
StringBuf_Printf(&buf, ", `option_parm%d`", j);
}
StringBuf_Printf(&buf, " FROM `%s` WHERE `id` = '%d'", schema_config.mail_db, mail_id);
if( SQL_ERROR == Sql_Query(sql_handle, StringBuf_Value(&buf))
|| SQL_SUCCESS != Sql_NextRow(sql_handle) )
{
if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `id`,`send_name`,`send_id`,`dest_name`,`dest_id`,`title`,`message`,`time`,`status`,`zeny`,`type` FROM `%s` WHERE `id` = '%d'", schema_config.mail_db, mail_id )
|| SQL_SUCCESS != Sql_NextRow(sql_handle) ){
Sql_ShowDebug(sql_handle);
Sql_FreeResult(sql_handle);
StringBuf_Destroy(&buf);
return false;
}
else
{
char* data;
}else{
Sql_GetData(sql_handle, 0, &data, NULL); msg->id = atoi(data);
Sql_GetData(sql_handle, 1, &data, NULL); safestrncpy(msg->send_name, data, NAME_LENGTH);
Sql_GetData(sql_handle, 2, &data, NULL); msg->send_id = atoi(data);
@ -199,27 +167,60 @@ static bool mail_loadmessage(int mail_id, struct mail_message* msg)
Sql_GetData(sql_handle, 7, &data, NULL); msg->timestamp = atoi(data);
Sql_GetData(sql_handle, 8, &data, NULL); msg->status = (mail_status)atoi(data);
Sql_GetData(sql_handle, 9, &data, NULL); msg->zeny = atoi(data);
Sql_GetData(sql_handle,10, &data, NULL); msg->item.amount = (short)atoi(data);
Sql_GetData(sql_handle,11, &data, NULL); msg->item.nameid = atoi(data);
Sql_GetData(sql_handle,12, &data, NULL); msg->item.refine = atoi(data);
Sql_GetData(sql_handle,13, &data, NULL); msg->item.attribute = atoi(data);
Sql_GetData(sql_handle,14, &data, NULL); msg->item.identify = atoi(data);
Sql_GetData(sql_handle,15, &data, NULL); msg->item.unique_id = strtoull(data, NULL, 10);
Sql_GetData(sql_handle,16, &data, NULL); msg->item.bound = atoi(data);
msg->item.expire_time = 0;
Sql_GetData(sql_handle,10, &data, NULL); msg->type = atoi(data);
for( j = 0; j < MAX_SLOTS; j++ )
{
Sql_GetData(sql_handle,17 + j, &data, NULL);
msg->item.card[j] = atoi(data);
if( msg->type == MAIL_INBOX_NORMAL && charserv_config.mail_return_days > 0 ){
msg->scheduled_deletion = msg->timestamp + charserv_config.mail_return_days * 24 * 60 * 60;
}else if( msg->type == MAIL_INBOX_RETURNED && charserv_config.mail_delete_days > 0 ){
msg->scheduled_deletion = msg->timestamp + charserv_config.mail_delete_days * 24 * 60 * 60;
}else{
msg->scheduled_deletion = 0;
}
for (j = 0; j < MAX_ITEM_RDM_OPT; j++) {
Sql_GetData(sql_handle, 17 + MAX_SLOTS + j * 3, &data, NULL);
msg->item.option[j].id = atoi(data);
Sql_GetData(sql_handle, 18 + MAX_SLOTS + j * 3, &data, NULL);
msg->item.option[j].value = atoi(data);
Sql_GetData(sql_handle, 19 + MAX_SLOTS + j * 3, &data, NULL);
msg->item.option[j].param = atoi(data);
Sql_FreeResult(sql_handle);
}
StringBuf_Init(&buf);
StringBuf_AppendStr(&buf, "SELECT `amount`,`nameid`,`refine`,`attribute`,`identify`,`unique_id`,`bound`");
for (j = 0; j < MAX_SLOTS; j++)
StringBuf_Printf(&buf, ",`card%d`", j);
for (j = 0; j < MAX_ITEM_RDM_OPT; ++j) {
StringBuf_Printf(&buf, ", `option_id%d`", j);
StringBuf_Printf(&buf, ", `option_val%d`", j);
StringBuf_Printf(&buf, ", `option_parm%d`", j);
}
StringBuf_Printf(&buf, " FROM `%s`", schema_config.mail_attachment_db);
StringBuf_Printf(&buf, " WHERE `id` = '%d'", mail_id);
StringBuf_AppendStr(&buf, " ORDER BY `index` ASC");
StringBuf_Printf(&buf, " LIMIT %d", MAIL_MAX_ITEM);
if( SQL_ERROR == Sql_Query(sql_handle, StringBuf_Value(&buf)) ){
Sql_ShowDebug(sql_handle);
Sql_FreeResult(sql_handle);
StringBuf_Destroy(&buf);
return false;
}
memset(msg->item, 0, sizeof(struct item) * MAIL_MAX_ITEM);
for( i = 0; i < MAIL_MAX_ITEM && SQL_SUCCESS == Sql_NextRow(sql_handle); i++ ){
Sql_GetData(sql_handle,0, &data, NULL); msg->item[i].amount = (short)atoi(data);
Sql_GetData(sql_handle,1, &data, NULL); msg->item[i].nameid = atoi(data);
Sql_GetData(sql_handle,2, &data, NULL); msg->item[i].refine = atoi(data);
Sql_GetData(sql_handle,3, &data, NULL); msg->item[i].attribute = atoi(data);
Sql_GetData(sql_handle,4, &data, NULL); msg->item[i].identify = atoi(data);
Sql_GetData(sql_handle,5, &data, NULL); msg->item[i].unique_id = strtoull(data, NULL, 10);
Sql_GetData(sql_handle,6, &data, NULL); msg->item[i].bound = atoi(data);
msg->item[i].expire_time = 0;
for( j = 0; j < MAX_SLOTS; j++ ){
Sql_GetData(sql_handle,7 + j, &data, NULL); msg->item[i].card[j] = atoi(data);
}
for( j = 0; j < MAX_ITEM_RDM_OPT; j++ ){
Sql_GetData(sql_handle, 7 + MAX_SLOTS + j * 3, &data, NULL); msg->item[i].option[j].id = atoi(data);
Sql_GetData(sql_handle, 8 + MAX_SLOTS + j * 3, &data, NULL); msg->item[i].option[j].value = atoi(data);
Sql_GetData(sql_handle, 9 + MAX_SLOTS + j * 3, &data, NULL); msg->item[i].option[j].param = atoi(data);
}
}
@ -229,27 +230,95 @@ static bool mail_loadmessage(int mail_id, struct mail_message* msg)
return true;
}
static int mail_timer_sub( int limit, enum mail_inbox_type type ){
struct{
int mail_id;
int char_id;
int account_id;
}mails[MAIL_MAX_INBOX];
int i, map_fd;
char* data;
struct online_char_data* character;
if( limit <= 0 ){
return 0;
}
memset(mails, 0, sizeof(mails));
if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `id`,`char_id`,`account_id` FROM `%s` `m` INNER JOIN `%s` `c` ON `c`.`char_id`=`m`.`dest_id` WHERE `type` = '%d' AND `time` <= UNIX_TIMESTAMP( NOW() - INTERVAL %d DAY ) ORDER BY `id` LIMIT %d", schema_config.mail_db, schema_config.char_db, type, limit, MAIL_MAX_INBOX + 1) ){
Sql_ShowDebug(sql_handle);
return 0;
}
if( Sql_NumRows(sql_handle) <= 0 ){
return 0;
}
for( i = 0; i < MAIL_MAX_INBOX && SQL_SUCCESS == Sql_NextRow(sql_handle); i++ ){
Sql_GetData(sql_handle, 0, &data, NULL); mails[i].mail_id = atoi(data);
Sql_GetData(sql_handle, 1, &data, NULL); mails[i].char_id = atoi(data);
Sql_GetData(sql_handle, 2, &data, NULL); mails[i].account_id = atoi(data);
}
Sql_FreeResult(sql_handle);
for( i = 0; i < MAIL_MAX_INBOX; i++ ){
if( mails[i].mail_id == 0 ){
break;
}
// Check for online players
if( ( character = (struct online_char_data*)idb_get(char_get_onlinedb(), mails[i].account_id) ) != NULL && character->server >= 0 ){
map_fd = map_server[character->server].fd;
}else{
map_fd = 0;
}
if( type == MAIL_INBOX_NORMAL ){
mapif_Mail_return( 0, mails[i].char_id, mails[i].mail_id );
mapif_Mail_delete( map_fd, mails[i].char_id, mails[i].mail_id, true );
}else if( type == MAIL_INBOX_RETURNED ){
mapif_Mail_delete( map_fd, mails[i].char_id, mails[i].mail_id, false );
}else{
// Should not happen
continue;
}
}
return 0;
}
int mail_return_timer( int tid, unsigned int tick, int id, intptr_t ptr ){
return mail_timer_sub( charserv_config.mail_return_days, MAIL_INBOX_NORMAL );
}
int mail_delete_timer( int tid, unsigned int tick, int id, intptr_t data ){
return mail_timer_sub( charserv_config.mail_delete_days, MAIL_INBOX_RETURNED );
}
/*==========================================
* Client Inbox Request
*------------------------------------------*/
static void mapif_Mail_sendinbox(int fd, uint32 char_id, unsigned char flag)
static void mapif_Mail_sendinbox(int fd, uint32 char_id, unsigned char flag, enum mail_inbox_type type)
{
struct mail_data md;
mail_fromsql(char_id, &md);
//FIXME: dumping the whole structure like this is unsafe [ultramage]
WFIFOHEAD(fd, sizeof(md) + 9);
WFIFOHEAD(fd, sizeof(md) + 10);
WFIFOW(fd,0) = 0x3848;
WFIFOW(fd,2) = sizeof(md) + 9;
WFIFOW(fd,2) = sizeof(md) + 10;
WFIFOL(fd,4) = char_id;
WFIFOB(fd,8) = flag;
memcpy(WFIFOP(fd,9),&md,sizeof(md));
WFIFOB(fd,9) = type;
memcpy(WFIFOP(fd,10),&md,sizeof(md));
WFIFOSET(fd,WFIFOW(fd,2));
}
static void mapif_parse_Mail_requestinbox(int fd)
{
mapif_Mail_sendinbox(fd, RFIFOL(fd,2), RFIFOB(fd,6));
mapif_Mail_sendinbox(fd, RFIFOL(fd,2), RFIFOB(fd,6), RFIFOB(fd,7));
}
/*==========================================
@ -265,38 +334,23 @@ static void mapif_parse_Mail_read(int fd)
/*==========================================
* Client Attachment Request
*------------------------------------------*/
static bool mail_DeleteAttach(int mail_id)
{
StringBuf buf;
int i;
StringBuf_Init(&buf);
StringBuf_Printf(&buf, "UPDATE `%s` SET `zeny` = '0', `nameid` = '0', `amount` = '0', `refine` = '0', `attribute` = '0', `identify` = '0'", schema_config.mail_db);
for (i = 0; i < MAX_SLOTS; i++)
StringBuf_Printf(&buf, ", `card%d` = '0'", i);
for (i = 0; i < MAX_ITEM_RDM_OPT; ++i) {
StringBuf_Printf(&buf, ", `option_id%d` = 0", i);
StringBuf_Printf(&buf, ", `option_val%d` = 0", i);
StringBuf_Printf(&buf, ", `option_parm%d` = 0", i);
}
StringBuf_Printf(&buf, " WHERE `id` = '%d'", mail_id);
if( SQL_ERROR == Sql_Query(sql_handle, StringBuf_Value(&buf)) )
{
static bool mail_DeleteAttach(int mail_id){
if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `id` = '%d'", schema_config.mail_attachment_db, mail_id ) ){
Sql_ShowDebug(sql_handle);
StringBuf_Destroy(&buf);
return false;
}
StringBuf_Destroy(&buf);
return true;
}
static void mapif_Mail_getattach(int fd, uint32 char_id, int mail_id)
static void mapif_Mail_getattach(int fd, uint32 char_id, int mail_id, int type)
{
struct mail_message msg;
if( ( type&MAIL_ATT_ALL ) == 0 ){
return;
}
if( !mail_loadmessage(mail_id, &msg) )
return;
@ -306,36 +360,77 @@ static void mapif_Mail_getattach(int fd, uint32 char_id, int mail_id)
if( msg.status != MAIL_READ )
return;
if( (msg.item.nameid < 1 || msg.item.amount < 1) && msg.zeny < 1 )
if( type & MAIL_ATT_ZENY ){
if( msg.zeny > 0 ){
if( SQL_ERROR == Sql_Query(sql_handle, "UPDATE `%s` SET `zeny` = 0 WHERE `id` = '%d'", schema_config.mail_db, mail_id ) ){
Sql_ShowDebug(sql_handle);
return;
}
}else{
type &= ~MAIL_ATT_ZENY;
}
}
if( type & MAIL_ATT_ITEM ){
int i;
ARR_FIND(0, MAIL_MAX_ITEM, i, msg.item[i].nameid > 0 && msg.item[i].amount > 0);
// No item was found
if( i == MAIL_MAX_ITEM ){
type &= ~MAIL_ATT_ITEM;
}else{
if( !mail_DeleteAttach(mail_id) ){
return;
}
}
}
if( type == MAIL_ATT_NONE )
return; // No Attachment
if( !mail_DeleteAttach(mail_id) )
return;
WFIFOHEAD(fd, sizeof(struct item) + 12);
WFIFOHEAD(fd, sizeof(struct item)*MAIL_MAX_ITEM + 16);
WFIFOW(fd,0) = 0x384a;
WFIFOW(fd,2) = sizeof(struct item) + 12;
WFIFOW(fd,2) = sizeof(struct item)*MAIL_MAX_ITEM + 16;
WFIFOL(fd,4) = char_id;
WFIFOL(fd,8) = (msg.zeny > 0)?msg.zeny:0;
memcpy(WFIFOP(fd,12), &msg.item, sizeof(struct item));
WFIFOL(fd,8) = mail_id;
if( type & MAIL_ATT_ZENY ){
WFIFOL(fd,12) = msg.zeny;
}else{
WFIFOL(fd, 12) = 0;
}
if( type & MAIL_ATT_ITEM ){
memcpy(WFIFOP(fd, 16), &msg.item, sizeof(struct item)*MAIL_MAX_ITEM);
}else{
memset(WFIFOP(fd, 16), 0, sizeof(struct item)*MAIL_MAX_ITEM);
}
WFIFOSET(fd,WFIFOW(fd,2));
}
static void mapif_parse_Mail_getattach(int fd)
{
mapif_Mail_getattach(fd, RFIFOL(fd,2), RFIFOL(fd,6));
mapif_Mail_getattach(fd, RFIFOL(fd,2), RFIFOL(fd,6),RFIFOB(fd,10));
}
/*==========================================
* Delete Mail
*------------------------------------------*/
static void mapif_Mail_delete(int fd, uint32 char_id, int mail_id)
static void mapif_Mail_delete(int fd, uint32 char_id, int mail_id, bool deleted)
{
bool failed = false;
if ( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `id` = '%d'", schema_config.mail_db, mail_id) )
{
Sql_ShowDebug(sql_handle);
failed = true;
if( !deleted ){
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);
failed = true;
}
}
// Only if the request came from a map-server and was not timer triggered for an offline character
if( fd <= 0 ){
return;
}
WFIFOHEAD(fd,11);
@ -348,7 +443,7 @@ static void mapif_Mail_delete(int fd, uint32 char_id, int mail_id)
static void mapif_parse_Mail_delete(int fd)
{
mapif_Mail_delete(fd, RFIFOL(fd,2), RFIFOL(fd,6));
mapif_Mail_delete(fd, RFIFOL(fd,2), RFIFOL(fd,6), false);
}
/*==========================================
@ -356,7 +451,7 @@ static void mapif_parse_Mail_delete(int fd)
*------------------------------------------*/
void mapif_Mail_new(struct mail_message *msg)
{
unsigned char buf[74];
unsigned char buf[75];
if( !msg || !msg->id )
return;
@ -366,7 +461,8 @@ void mapif_Mail_new(struct mail_message *msg)
WBUFL(buf,6) = msg->id;
memcpy(WBUFP(buf,10), msg->send_name, NAME_LENGTH);
memcpy(WBUFP(buf,34), msg->title, MAIL_TITLE_LENGTH);
chmapif_sendall(buf, 74);
WBUFB(buf,74) = msg->type;
chmapif_sendall(buf, 75);
}
/*==========================================
@ -381,7 +477,8 @@ static void mapif_Mail_return(int fd, uint32 char_id, int mail_id)
{
if( msg.dest_id != char_id)
return;
else if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `id` = '%d'", schema_config.mail_db, 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
{
@ -398,6 +495,7 @@ static void mapif_Mail_return(int fd, uint32 char_id, int mail_id)
safestrncpy(msg.title, temp_, MAIL_TITLE_LENGTH);
msg.status = MAIL_NEW;
msg.type = MAIL_INBOX_RETURNED;
msg.timestamp = time(NULL);
new_mail = mail_savemessage(&msg);
@ -405,6 +503,11 @@ static void mapif_Mail_return(int fd, uint32 char_id, int mail_id)
}
}
// Only if the request came from a map-server and was not timer triggered for an offline character
if( fd <= 0 ){
return;
}
WFIFOHEAD(fd,11);
WFIFOW(fd,0) = 0x384c;
WFIFOL(fd,2) = char_id;
@ -452,12 +555,18 @@ static void mapif_parse_Mail_send(int fd)
if ( SQL_SUCCESS == Sql_NextRow(sql_handle) )
{
char *data;
#if PACKETVER < 20150513
Sql_GetData(sql_handle, 0, &data, NULL);
if (atoi(data) != account_id)
{ // Cannot send mail to char in the same account
Sql_GetData(sql_handle, 1, &data, NULL);
msg.dest_id = atoi(data);
}
#else
// In RODEX you can even send mails to yourself
Sql_GetData(sql_handle, 1, &data, NULL);
msg.dest_id = atoi(data);
#endif
}
Sql_FreeResult(sql_handle);
msg.status = MAIL_NEW;
@ -469,7 +578,7 @@ static void mapif_parse_Mail_send(int fd)
mapif_Mail_new(&msg); // notify recipient
}
void mail_sendmail(int send_id, const char* send_name, int dest_id, const char* dest_name, const char* title, const char* body, int zeny, struct item *item)
void mail_sendmail(int send_id, const char* send_name, int dest_id, const char* dest_name, const char* title, const char* body, int zeny, struct item *item, int amount)
{
struct mail_message msg;
memset(&msg, 0, sizeof(struct mail_message));
@ -481,15 +590,56 @@ void mail_sendmail(int send_id, const char* send_name, int dest_id, const char*
safestrncpy(msg.title, title, MAIL_TITLE_LENGTH);
safestrncpy(msg.body, body, MAIL_BODY_LENGTH);
msg.zeny = zeny;
if( item != NULL )
memcpy(&msg.item, item, sizeof(struct item));
if( item != NULL ){
int i;
for( i = 0; i < amount && i < MAIL_MAX_ITEM; i++ ){
memcpy(&msg.item[i], &item[i], sizeof(struct item));
}
}
msg.timestamp = time(NULL);
msg.type = MAIL_INBOX_NORMAL;
mail_savemessage(&msg);
mapif_Mail_new(&msg);
}
static void mapif_Mail_receiver_send( int fd, int requesting_char_id, int char_id, int class_, int base_level, const char* name ){
WFIFOHEAD(fd,38);
WFIFOW(fd,0) = 0x384e;
WFIFOL(fd,2) = requesting_char_id;
WFIFOL(fd,6) = char_id;
WFIFOW(fd,10) = class_;
WFIFOW(fd,12) = base_level;
strncpy(WFIFOCP(fd, 14), name, NAME_LENGTH);
WFIFOSET(fd,38);
}
static void mapif_parse_Mail_receiver_check( int fd ){
char name[NAME_LENGTH], esc_name[NAME_LENGTH * 2 + 1];
uint32 char_id = 0;
uint16 class_ = 0, base_level = 0;
safestrncpy( name, RFIFOCP(fd, 6), NAME_LENGTH );
// Try to find the Dest Char by Name
Sql_EscapeStringLen( sql_handle, esc_name, name, strnlen( name, NAME_LENGTH ) );
if( SQL_ERROR == Sql_Query( sql_handle, "SELECT `char_id`,`class`,`base_level` 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;
Sql_GetData(sql_handle, 0, &data, NULL); char_id = atoi(data);
Sql_GetData(sql_handle, 1, &data, NULL); class_ = atoi(data);
Sql_GetData(sql_handle, 2, &data, NULL); base_level = atoi(data);
}
Sql_FreeResult(sql_handle);
mapif_Mail_receiver_send( fd, RFIFOL(fd, 2), char_id, class_, base_level, name );
}
/*==========================================
* Packets From Map Server
*------------------------------------------*/
@ -503,6 +653,7 @@ int inter_mail_parse_frommap(int fd)
case 0x304b: mapif_parse_Mail_delete(fd); break;
case 0x304c: mapif_parse_Mail_return(fd); break;
case 0x304d: mapif_parse_Mail_send(fd); break;
case 0x304e: mapif_parse_Mail_receiver_check(fd); break;
default:
return 0;
}

View File

@ -4,8 +4,11 @@
#ifndef _INT_MAIL_SQL_H_
#define _INT_MAIL_SQL_H_
int mail_return_timer( int tid, unsigned int tick, int id, intptr_t data );
int mail_delete_timer( int tid, unsigned int tick, int id, intptr_t data );
int inter_mail_parse_frommap(int fd);
void mail_sendmail(int send_id, const char* send_name, int dest_id, const char* dest_name, const char* title, const char* body, int zeny, struct item *item);
void mail_sendmail(int send_id, const char* send_name, int dest_id, const char* dest_name, const char* title, const char* body, int zeny, struct item *item, int amount);
int inter_mail_sql_init(void);
void inter_mail_sql_final(void);

View File

@ -49,7 +49,7 @@ int inter_recv_packet_length[] = {
6,-1, 0, 0, 0, 0, 0, 0, 10,-1, 0, 0, 0, 0, 0, 0, // 3010-
-1,10,-1,14, 15+NAME_LENGTH,19, 6,-1, 14,14, 6, 0, 0, 0, 0, 0, // 3020- Party
-1, 6,-1,-1, 55,19, 6,-1, 14,-1,-1,-1, 18,19,186,-1, // 3030-
-1, 9, 0, 0, 0, 0, 0, 0, 7, 6,10,10, 10,-1, 0, 0, // 3040-
-1, 9, 0, 0, 0, 0, 0, 0, 8, 6,11,10, 10,-1,6+NAME_LENGTH, 0, // 3040-
-1,-1,10,10, 0,-1,12, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3050- Auction System [Zephyrus]
6,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3060- Quest system [Kevin] [Inkfish]
-1,10, 6,-1, 0, 0, 0, 0, 0, 0, 0, 0, -1,10, 6,-1, // 3070- Mercenary packets [Zephyrus], Elemental packets [pakpil]

View File

@ -140,7 +140,14 @@
//Mail System
#define MAIL_MAX_INBOX 30
#define MAIL_TITLE_LENGTH 40
#if PACKETVER < 20150513
#define MAIL_BODY_LENGTH 200
#define MAIL_MAX_ITEM 1
#else
#define MAIL_BODY_LENGTH 500
#define MAIL_MAX_ITEM 5
#define MAIL_PAGE_SIZE 7
#endif
//Mercenary System
#define MC_SKILLBASE 8201
@ -500,6 +507,19 @@ typedef enum mail_status {
MAIL_READ,
} mail_status;
enum mail_inbox_type {
MAIL_INBOX_NORMAL = 0,
MAIL_INBOX_ACCOUNT,
MAIL_INBOX_RETURNED
};
enum mail_attachment_type {
MAIL_ATT_NONE = 0,
MAIL_ATT_ZENY = 1,
MAIL_ATT_ITEM = 2,
MAIL_ATT_ALL = MAIL_ATT_ZENY | MAIL_ATT_ITEM
};
struct mail_message {
int id;
uint32 send_id; //hold char_id of sender
@ -508,12 +528,14 @@ struct mail_message {
char dest_name[NAME_LENGTH]; //receiver nickname
char title[MAIL_TITLE_LENGTH];
char body[MAIL_BODY_LENGTH];
int type; // enum mail_inbox_type
time_t scheduled_deletion;
mail_status status;
time_t timestamp; // marks when the message was sent
uint32 zeny;
struct item item;
struct item item[MAIL_MAX_ITEM];
};
struct mail_data {

View File

@ -8407,6 +8407,10 @@ static const struct _battle_data {
{ "dispel_song", &battle_config.dispel_song, 0, 0, 1, },
{ "guild_maprespawn_clones", &battle_config.guild_maprespawn_clones, 0, 0, 1, },
{ "hide_fav_sell", &battle_config.hide_fav_sell, 0, 0, 1, },
{ "mail_daily_count", &battle_config.mail_daily_count, 100, 0, INT32_MAX, },
{ "mail_zeny_fee", &battle_config.mail_zeny_fee, 2, 0, 100, },
{ "mail_attachment_price", &battle_config.mail_attachment_price, 2500, 0, INT32_MAX, },
{ "mail_attachment_weight", &battle_config.mail_attachment_weight, 2000, 0, INT32_MAX, },
#include "../custom/battle_config_init.inc"
};

View File

@ -618,6 +618,10 @@ extern struct Battle_Config
int dispel_song; //Can songs be dispelled?
int guild_maprespawn_clones; // Should clones be killed by maprespawnguildid?
int hide_fav_sell;
int mail_daily_count;
int mail_zeny_fee;
int mail_attachment_price;
int mail_attachment_weight;
#include "../custom/battle_config_struct.inc"
} battle_config;

View File

@ -72,6 +72,15 @@ static unsigned int clif_cryptKey[3]; // Used keys
static unsigned short clif_parse_cmd(int fd, struct map_session_data *sd);
static bool clif_session_isValid(struct map_session_data *sd);
#if PACKETVER >= 20150513
enum mail_type {
MAIL_TYPE_TEXT = 0x0,
MAIL_TYPE_ZENY = 0x2,
MAIL_TYPE_ITEM = 0x4,
MAIL_TYPE_NPC = 0x8
};
#endif
/** Converts item type to display it on client if necessary.
* @param nameid: Item ID
* @return item type. For IT_PETEGG will be displayed as IT_WEAPON. If Shadow Weapon of IT_SHADOWGEAR as IT_WEAPON and else as IT_ARMOR
@ -10538,6 +10547,12 @@ void clif_parse_LoadEndAck(int fd,struct map_session_data *sd)
pc_show_questinfo_reinit(sd);
pc_show_questinfo(sd);
#if PACKETVER >= 20150513
if( sd->mail.inbox.unread ){
clif_Mail_new(sd, 0, NULL, NULL);
}
#endif
sd->state.connect_new = 0;
sd->state.changemap = false;
}
@ -11438,6 +11453,8 @@ void clif_parse_NpcClicked(int fd,struct map_session_data *sd)
#endif
if (pc_cant_act2(sd) || sd->npc_id)
return;
if( sd->state.mail_writing )
return;
bl = map_id2bl(RFIFOL(fd,info->pos[0]));
//type = RFIFOB(fd,info->pos[1]);
@ -11681,10 +11698,24 @@ void clif_parse_TradeRequest(int fd,struct map_session_data *sd)
if(!sd->chatID && pc_cant_act(sd))
return; //You can trade while in a chatroom.
// @noask [LuzZza]
if(t_sd && t_sd->state.noask) {
clif_noask_sub(sd, t_sd, 0);
return;
if(t_sd){
// @noask [LuzZza]
if(t_sd->state.noask) {
clif_noask_sub(sd, t_sd, 0);
return;
}
if (t_sd->state.mail_writing) {
int old = sd->trade_partner;
// Fake trading
sd->trade_partner = t_sd->status.account_id;
clif_tradestart(sd, 5);
// Restore old state
sd->trade_partner = old;
return;
}
}
if( battle_config.basic_skill_check && pc_checkskill(sd,NV_BASIC) < 1 && pc_checkskill(sd, SU_BASIC_SKILL) < 1) {
@ -14759,62 +14790,144 @@ void clif_parse_Check(int fd, struct map_session_data *sd)
/// By Zephyrus
///
/// Notification about the result of adding an item to mail (ZC_ACK_MAIL_ADD_ITEM).
/// 0255 <index>.W <result>.B
/// Notification about the result of adding an item to mail
/// 0255 <index>.W <result>.B (ZC_ACK_MAIL_ADD_ITEM)
/// result:
/// 0 = success
/// 1 = failure
void clif_Mail_setattachment(int fd, int index, uint8 flag)
/// 0a05 <result>.B <index>.W <amount>.W <item id>.W <item type>.B <identified>.B <damaged>.B <refine>.B (ZC_ACK_ADD_ITEM_TO_MAIL)
/// { <card id>.W }*4 { <option index>.W <option value>.W <option parameter>.B }*5
/// result:
/// 0 = success
/// 1 = over weight
/// 2 = fatal error
void clif_Mail_setattachment(struct map_session_data* sd, int index, int amount, uint8 flag)
{
int fd = sd->fd;
#if PACKETVER < 20150513
WFIFOHEAD(fd,packet_len(0x255));
WFIFOW(fd,0) = 0x255;
WFIFOW(fd,2) = index;
WFIFOB(fd,4) = flag;
WFIFOSET(fd,packet_len(0x255));
#else
struct item* item = &sd->inventory.u.items_inventory[index-2];
int i, total = 0;
WFIFOHEAD(fd, 53);
WFIFOW(fd, 0) = 0xa05;
WFIFOB(fd, 2) = flag;
WFIFOW(fd, 3) = index;
WFIFOW(fd, 5) = amount;
WFIFOW(fd, 7) = item->nameid;
WFIFOB(fd, 9) = itemtype(item->nameid);
WFIFOB(fd, 10) = item->identify;
WFIFOB(fd, 11) = item->attribute;
WFIFOB(fd, 12) = item->refine;
clif_addcards(WFIFOP(fd,13), item);
clif_add_random_options(WFIFOP(fd,21), item);
for( i = 0; i < MAIL_MAX_ITEM; i++ ){
if( sd->mail.item[i].nameid == 0 ){
break;
}
total += sd->mail.item[i].amount * ( sd->inventory_data[sd->mail.item[i].index]->weight / 10 );
}
WFIFOW(fd, 46) = total;
// 5 empty unknown bytes
memset(WFIFOP(fd, 48), 0, 5);
WFIFOSET(fd, 53);
#endif
}
/// Notification about the result of retrieving a mail attachment (ZC_MAIL_REQ_GET_ITEM).
/// 0245 <result>.B
/// Notification about the result of retrieving a mail attachment
/// 0245 <result>.B (ZC_MAIL_REQ_GET_ITEM)
/// result:
/// 0 = success
/// 1 = failure
/// 2 = too many items
void clif_Mail_getattachment(int fd, uint8 result)
{
/// 09f2 <mail id>.Q <mail tab>.B <result>.B (ZC_ACK_ZENY_FROM_MAIL)
/// 09f4 <mail id>.Q <mail tab>.B <result>.B (ZC_ACK_ITEM_FROM_MAIL)
void clif_mail_getattachment(struct map_session_data* sd, struct mail_message *msg, uint8 result, enum mail_attachment_type type) {
int fd = sd->fd;
#if PACKETVER < 20150513
WFIFOHEAD(fd,packet_len(0x245));
WFIFOW(fd,0) = 0x245;
WFIFOB(fd,2) = result;
WFIFOSET(fd,packet_len(0x245));
#else
switch( type ){
case MAIL_ATT_ITEM:
case MAIL_ATT_ZENY:
break;
default:
return;
}
WFIFOHEAD(fd, 12);
WFIFOW(fd, 0) = type == MAIL_ATT_ZENY ? 0x9f2 : 0x9f4;
WFIFOQ(fd, 2) = msg->id;
WFIFOB(fd, 10) = msg->type;
WFIFOB(fd, 11) = result;
WFIFOSET(fd, 12);
clif_Mail_refreshinbox( sd, msg->type, 0 );
#endif
}
/// Notification about the result of sending a mail (ZC_MAIL_REQ_SEND).
/// 0249 <result>.B
/// Notification about the result of sending a mail
/// 0249 <result>.B (ZC_MAIL_REQ_SEND)
/// result:
/// 0 = success
/// 1 = recipinent does not exist
void clif_Mail_send(int fd, bool fail)
{
/// 09ed <result>.B (ZC_ACK_WRITE_MAIL)
void clif_Mail_send(struct map_session_data* sd, enum mail_send_result result){
int fd = sd->fd;
#if PACKETVER < 20150513
WFIFOHEAD(fd,packet_len(0x249));
WFIFOW(fd,0) = 0x249;
WFIFOB(fd,2) = fail;
WFIFOB(fd,2) = result != WRITE_MAIL_SUCCESS;
WFIFOSET(fd,packet_len(0x249));
#else
WFIFOHEAD(fd, 3);
WFIFOW(fd, 0) = 0x9ed;
WFIFOB(fd, 2) = result;
WFIFOSET(fd, 3);
#endif
}
/// Notification about the result of deleting a mail (ZC_ACK_MAIL_DELETE).
/// 0257 <mail id>.L <result>.W
/// Notification about the result of deleting a mail.
/// 0257 <mail id>.L <result>.W (ZC_ACK_MAIL_DELETE)
/// result:
/// 0 = success
/// 1 = failure
void clif_Mail_delete(int fd, int mail_id, short fail)
{
// 09f6 <mail tab>.B <mail id>.Q (ZC_ACK_DELETE_MAIL)
void clif_mail_delete( struct map_session_data* sd, struct mail_message *msg, bool success ){
int fd = sd->fd;
#if PACKETVER < 20150513
WFIFOHEAD(fd, packet_len(0x257));
WFIFOW(fd,0) = 0x257;
WFIFOL(fd,2) = mail_id;
WFIFOW(fd,6) = fail;
WFIFOL(fd,2) = msg->id;
WFIFOW(fd,6) = !success;
WFIFOSET(fd, packet_len(0x257));
#else
// This is only a success notification
if( success ){
WFIFOHEAD(fd, 11);
WFIFOW(fd, 0) = 0x9f6;
WFIFOB(fd, 2) = msg->type;
WFIFOQ(fd, 3) = msg->id;
WFIFOSET(fd, 11);
}
#endif
}
@ -14832,20 +14945,26 @@ void clif_Mail_return(int fd, int mail_id, short fail)
WFIFOSET(fd,packet_len(0x274));
}
/// Notification about new mail (ZC_MAIL_RECEIVE).
/// 024a <mail id>.L <title>.40B <sender>.24B
void clif_Mail_new(int fd, int mail_id, const char *sender, const char *title)
{
/// Notification about new mail.
/// 024a <mail id>.L <title>.40B <sender>.24B (ZC_MAIL_RECEIVE)
/// 09e7 <result>.B (ZC_NOTIFY_UNREADMAIL)
void clif_Mail_new(struct map_session_data* sd, int mail_id, const char *sender, const char *title){
int fd = sd->fd;
#if PACKETVER < 20150513
WFIFOHEAD(fd,packet_len(0x24a));
WFIFOW(fd,0) = 0x24a;
WFIFOL(fd,2) = mail_id;
safestrncpy(WFIFOCP(fd,6), title, MAIL_TITLE_LENGTH);
safestrncpy(WFIFOCP(fd,46), sender, NAME_LENGTH);
WFIFOSET(fd,packet_len(0x24a));
#else
WFIFOHEAD(fd,3);
WFIFOW(fd,0) = 0x9e7;
WFIFOB(fd,2) = sd->mail.inbox.unread > 0 || sd->mail.inbox.unchecked > 0; // unread
WFIFOSET(fd,3);
#endif
}
/// Opens/closes the mail window (ZC_MAIL_WINDOWS).
/// 0260 <type>.L
/// type:
@ -14859,14 +14978,17 @@ void clif_Mail_window(int fd, int flag)
WFIFOSET(fd,packet_len(0x260));
}
/// Lists mails stored in inbox (ZC_MAIL_REQ_GET_LIST).
/// 0240 <packet len>.W <amount>.L { <mail id>.L <title>.40B <read>.B <sender>.24B <time>.L }*amount
/// Lists mails stored in inbox.
/// 0240 <packet len>.W <amount>.L { <mail id>.L <title>.40B <read>.B <sender>.24B <time>.L }*amount (ZC_MAIL_REQ_GET_LIST)
/// read:
/// 0 = unread
/// 1 = read
void clif_Mail_refreshinbox(struct map_session_data *sd)
{
/// 09f0 <packet len>.W <type>.B <amount>.B <last page>.B (ZC_ACK_MAIL_LIST)
/// { <mail id>.Q <read>.B <type>.B <sender>.24B <received>.L <expires>.L <title length>.W <title>.?B }*
/// 0a7d <packet len>.W <type>.B <amount>.B <last page>.B (ZC_ACK_MAIL_LIST2)
/// { <mail id>.Q <read>.B <type>.B <sender>.24B <received>.L <expires>.L <title length>.W <title>.?B }*
void clif_Mail_refreshinbox(struct map_session_data *sd,enum mail_inbox_type type,int64 mailID){
#if PACKETVER < 20150513
int fd = sd->fd;
struct mail_data *md = &sd->mail.inbox;
struct mail_message *msg;
@ -14898,29 +15020,176 @@ void clif_Mail_refreshinbox(struct map_session_data *sd)
safesnprintf(output,sizeof(output),"Inbox is full (Max %d). Delete some mails.", MAIL_MAX_INBOX);
clif_messagecolor(&sd->bl, color_table[COLOR_LIGHT_GREEN], output, false, SELF);
}
#else
int fd = sd->fd;
struct mail_data *md = &sd->mail.inbox;
struct mail_message *msg;
int i, j, k, offset, titleLength;
uint8 mailType, amount, remaining;
uint32 now = (uint32)time(NULL);
#if PACKETVER >= 20160601
int cmd = 0xa7d;
#else
int cmd = 0x9f0;
#endif
if( battle_config.mail_daily_count ){
mail_refresh_remaining_amount(sd);
}
// If a starting mail id was sent
if( mailID != 0 ){
ARR_FIND( 0, md->amount, i, md->msg[i].id == mailID );
// Unknown mail
if( i == md->amount ){
// Ignore the request for now
return; // TODO: Should we just show the first page instead?
}
// It was actually the oldest/first mail, there is no further page
if( i == 0 ){
return;
}
// We actually want the next/older mail
i -= 1;
}else{
i = md->amount;
}
// Count the remaining mails from the starting mail or the beginning
// Only count mails of the target type and those that should not have been deleted already
for( j = i, remaining = 0; j >= 0; j-- ){
msg = &md->msg[j];
if (msg->id < 1)
continue;
if (msg->type != type)
continue;
if (msg->scheduled_deletion > 0 && msg->scheduled_deletion <= now)
continue;
remaining++;
}
if( remaining > MAIL_PAGE_SIZE ){
amount = MAIL_PAGE_SIZE;
}else{
amount = remaining;
}
WFIFOHEAD(fd, 7 + ((44 + MAIL_TITLE_LENGTH) * amount));
WFIFOW(fd, 0) = cmd;
WFIFOB(fd, 4) = type;
WFIFOB(fd, 5) = amount;
WFIFOB(fd, 6) = ( remaining < MAIL_PAGE_SIZE ); // last page
for( offset = 7, amount = 0; i >= 0; i-- ){
msg = &md->msg[i];
if (msg->id < 1)
continue;
if (msg->type != type)
continue;
if (msg->scheduled_deletion > 0 && msg->scheduled_deletion <= now)
continue;
WFIFOQ(fd, offset + 0) = (uint64)msg->id;
WFIFOB(fd, offset + 8) = (msg->status != MAIL_UNREAD);
mailType = MAIL_TYPE_TEXT; // Normal letter
if( msg->zeny > 0 ){
mailType |= MAIL_TYPE_ZENY; // Mail contains zeny
}
for( k = 0; k < MAIL_MAX_ITEM; k++ ){
if( msg->item[k].nameid > 0 ){
mailType |= MAIL_TYPE_ITEM; // Mail contains items
break;
}
}
// If it came from an npc?
//mailType |= MAIL_TYPE_NPC;
WFIFOB(fd, offset + 9) = mailType;
safestrncpy(WFIFOCP(fd, offset + 10), msg->send_name, NAME_LENGTH);
// How much time has passed since you received the mail
WFIFOL(fd, offset + 34 ) = now - (uint32)msg->timestamp;
// If automatic return/deletion of mails is enabled, notify the client when it will kick in
if( msg->scheduled_deletion > 0 ){
WFIFOL(fd, offset + 38) = (uint32)msg->scheduled_deletion - now;
}else{
// Fake the scheduled deletion to one year in the future
// Sadly the client always displays the scheduled deletion after 24 hours no matter how high this value gets [Lemongrass]
WFIFOL(fd, offset + 38) = 365 * 24 * 60 * 60;
}
WFIFOW(fd, offset + 42) = titleLength = (int16)(strlen(msg->title) + 1);
safestrncpy(WFIFOCP(fd, offset + 44), msg->title, titleLength);
offset += 44 + titleLength;
}
WFIFOW(fd, 2) = (int16)offset;
WFIFOSET(fd, offset);
#endif
}
/// Mail inbox list request (CZ_MAIL_GET_LIST).
/// 023f
void clif_parse_Mail_refreshinbox(int fd, struct map_session_data *sd)
{
/// Mail inbox list request.
/// 023f (CZ_MAIL_GET_LIST)
/// 09e8 <mail tab>.B <mail id>.Q (CZ_OPEN_MAILBOX)
/// 09ee <mail tab>.B <mail id>.Q (CZ_REQ_NEXT_MAIL_LIST)
/// 09ef <mail tab>.B <mail id>.Q (CZ_REQ_REFRESH_MAIL_LIST)
void clif_parse_Mail_refreshinbox(int fd, struct map_session_data *sd){
#if PACKETVER < 20150513
struct mail_data* md = &sd->mail.inbox;
if( md->amount < MAIL_MAX_INBOX && (md->full || sd->mail.changed) )
intif_Mail_requestinbox(sd->status.char_id, 1);
intif_Mail_requestinbox(sd->status.char_id, 1, MAIL_INBOX_NORMAL);
else
clif_Mail_refreshinbox(sd);
clif_Mail_refreshinbox(sd, MAIL_INBOX_NORMAL,0);
mail_removeitem(sd, 0);
mail_removezeny(sd, 0);
mail_removeitem(sd, 0, sd->mail.item[0].index, sd->mail.item[0].amount);
mail_removezeny(sd, false);
#else
uint8 openType = RFIFOB(fd, 2);
uint64 mailId = RFIFOQ(fd, 3);
switch( openType ){
case MAIL_INBOX_NORMAL:
case MAIL_INBOX_ACCOUNT:
case MAIL_INBOX_RETURNED:
break;
default:
// Unknown type: ignore request
return;
}
if( sd->mail.changed || RFIFOW(fd,0) == 0x9ef ){
intif_Mail_requestinbox(sd->status.char_id, 1, openType);
return;
}
// If it is not a next page request
if( RFIFOW(fd,0) != 0x9ee ){
mailId = 0;
}
clif_Mail_refreshinbox(sd,(enum mail_inbox_type)openType,mailId);
#endif
}
/// Opens a mail (ZC_MAIL_REQ_OPEN).
/// 0242 <packet len>.W <mail id>.L <title>.40B <sender>.24B <time>.L <zeny>.L
/// Opens a mail
/// 0242 <packet len>.W <mail id>.L <title>.40B <sender>.24B <time>.L <zeny>.L (ZC_MAIL_REQ_OPEN)
/// <amount>.L <name id>.W <item type>.W <identified>.B <damaged>.B <refine>.B
/// <card1>.W <card2>.W <card3>.W <card4>.W <message>.?B
/// 09eb <packet len>.W <type>.B <mail id>.Q <message length>.W <zeny>.Q <item count>.B (ZC_ACK_READ_MAIL)
/// { }*n
// TODO: Packet description => for repeated block
void clif_Mail_read(struct map_session_data *sd, int mail_id)
{
int i, fd = sd->fd;
@ -14932,15 +15201,21 @@ void clif_Mail_read(struct map_session_data *sd, int mail_id)
return;
} else {
struct mail_message *msg = &sd->mail.inbox.msg[i];
struct item *item = &msg->item;
struct item *item;
struct item_data *data;
int msg_len = strlen(msg->body), len;
int msg_len = strlen(msg->body), len, count = 0;
#if PACKETVER >= 20150513
int offset, j, itemsize;
#endif
if( msg_len == 0 ) {
strcpy(msg->body, "(no message)");
strcpy(msg->body, "(no message)"); // TODO: confirm for RODEX
msg_len = strlen(msg->body);
}
#if PACKETVER < 20150513
item = &msg->item[0];
len = 101 + msg_len;
WFIFOHEAD(fd,len);
@ -14969,21 +15244,72 @@ void clif_Mail_read(struct map_session_data *sd, int mail_id)
WFIFOB(fd,99) = (unsigned char)msg_len;
safestrncpy(WFIFOCP(fd,100), msg->body, msg_len + 1);
WFIFOSET(fd,len);
#else
// Count the attached items
ARR_FIND( 0, MAIL_MAX_ITEM, count, msg->item[count].nameid == 0 );
msg_len += 1; // Zero Termination
itemsize = 24 + 5 * MAX_ITEM_RDM_OPT;
len = 24 + msg_len+1 + itemsize * count;
WFIFOHEAD(fd, len);
WFIFOW(fd, 0) = 0x9eb;
WFIFOW(fd, 2) = len;
WFIFOB(fd, 4) = msg->type;
WFIFOQ(fd, 5) = msg->id;
WFIFOW(fd, 13) = msg_len;
WFIFOQ(fd, 15) = msg->zeny;
WFIFOB(fd, 23) = (uint8)count; // item count
safestrncpy(WFIFOCP(fd, 24), msg->body, msg_len);
offset = 24 + msg_len;
for (j = 0; j < MAIL_MAX_ITEM; j++) {
item = &msg->item[j];
if (item->nameid > 0 && item->amount > 0 && (data = itemdb_exists(item->nameid)) != NULL) {
WFIFOW(fd, offset) = item->amount;
WFIFOW(fd, offset + 2) = (data->view_id) ? data->view_id : item->nameid;
WFIFOB(fd, offset + 4) = item->identify;
WFIFOB(fd, offset + 5) = item->attribute;
WFIFOB(fd, offset + 6) = item->refine;
clif_addcards(WFIFOP(fd, offset + 7), item);
// 4B unsigned long location
WFIFOB(fd, offset + 15 + 4) = data->type;
// 2B unsigned short wItemSpriteNumber
//WFIFOW(fd, offset + 15 + 5) = data->view_id;
// 2B unsigned short bindOnEquipType
clif_add_random_options(WFIFOP(fd, offset + 15 + 9 ), item);
offset += itemsize;
}
}
WFIFOSET(fd, len);
#endif
if (msg->status == MAIL_UNREAD) {
msg->status = MAIL_READ;
intif_Mail_read(mail_id);
clif_parse_Mail_refreshinbox(fd, sd);
sd->mail.inbox.unread--;
clif_Mail_new(sd, 0, msg->send_name, msg->title);
}
}
}
/// Request to open a mail (CZ_MAIL_OPEN).
/// 0241 <mail id>.L
void clif_parse_Mail_read(int fd, struct map_session_data *sd)
{
/// Request to open a mail.
/// 0241 <mail id>.L (CZ_MAIL_OPEN)
/// 09ea <mail tab>.B <mail id>.Q (CZ_REQ_READ_MAIL)
void clif_parse_Mail_read(int fd, struct map_session_data *sd){
#if PACKETVER < 20150513
int mail_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]);
#else
uint8 openType = RFIFOB(fd, 2);
int mail_id = (int)RFIFOQ(fd, 3);
#endif
if( mail_id <= 0 )
return;
@ -14993,14 +15319,91 @@ void clif_parse_Mail_read(int fd, struct map_session_data *sd)
clif_Mail_read(sd, mail_id);
}
/// Allow a player to begin writing a mail
/// 0a12 <receiver>.24B <success>.B (ZC_ACK_OPEN_WRITE_MAIL)
void clif_send_Mail_beginwrite_ack( struct map_session_data *sd, char* name, bool success ){
int fd = sd->fd;
/// Request to receive mail's attachment (CZ_MAIL_GET_ITEM).
/// 0244 <mail id>.L
void clif_parse_Mail_getattach(int fd, struct map_session_data *sd)
{
int mail_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]);
WFIFOHEAD(fd, 27);
WFIFOW(fd, 0) = 0xa12;
safestrncpy(WFIFOCP(fd, 2), name, NAME_LENGTH);
WFIFOB(fd, 26) = success;
WFIFOSET(fd, 27);
}
/// Request to start writing a mail
/// 0a08 <receiver>.24B (CZ_REQ_OPEN_WRITE_MAIL)
void clif_parse_Mail_beginwrite( int fd, struct map_session_data *sd ){
char name[NAME_LENGTH];
safestrncpy(name, RFIFOCP(fd, 2), NAME_LENGTH);
if( sd->state.storage_flag || sd->state.mail_writing || sd->trade_partner ){
clif_send_Mail_beginwrite_ack(sd, name, false);
return;
}
mail_clear(sd);
sd->state.mail_writing = true;
clif_send_Mail_beginwrite_ack(sd, name,true);
}
/// Notification that the client cancelled writing a mail
/// 0a03 (CZ_REQ_CANCEL_WRITE_MAIL)
void clif_parse_Mail_cancelwrite( int fd, struct map_session_data *sd ){
sd->state.mail_writing = false;
}
/// Give the client information about the recipient, if available
/// 0a14 <char id>.L <class>.W <base level>.W (ZC_CHECK_RECEIVE_CHARACTER_NAME)
/// 0a51 <char id>.L <class>.W <base level>.W <name>.24B (ZC_CHECK_RECEIVE_CHARACTER_NAME2)
void clif_Mail_Receiver_Ack( struct map_session_data* sd, uint32 char_id, short class_, uint32 level, const char* name ){
int fd = sd->fd;
#if PACKETVER <= 20160302
int cmd = 0xa14;
#else
int cmd = 0xa51;
#endif
WFIFOHEAD(fd, packet_len(cmd));
WFIFOW(fd, 0) = cmd;
WFIFOL(fd, 2) = char_id;
WFIFOW(fd, 6) = class_;
WFIFOW(fd, 8) = level;
#if PACKETVER >= 20160302
strncpy(WFIFOCP(fd, 10), name, NAME_LENGTH);
#endif
WFIFOSET(fd, packet_len(cmd));
}
/// Request information about the recipient
/// 0a13 <name>.24B (CZ_CHECK_RECEIVE_CHARACTER_NAME)
void clif_parse_Mail_Receiver_Check(int fd, struct map_session_data *sd) {
static char name[NAME_LENGTH];
safestrncpy(name, RFIFOCP(fd, 2), NAME_LENGTH);
intif_mail_checkreceiver(sd, name);
}
/// Request to receive mail's attachment.
/// 0244 <mail id>.L (CZ_MAIL_GET_ITEM)
/// 09f1 <mail id>.Q <mail tab>.B (CZ_REQ_ZENY_FROM_MAIL)
/// 09f3 <mail id>.Q <mail tab>.B (CZ_REQ_ITEM_FROM_MAIL)
void clif_parse_Mail_getattach( int fd, struct map_session_data *sd ){
int i;
bool fail = false;
struct mail_message* msg;
#if PACKETVER < 20150513
int mail_id = RFIFOL(fd, packet_db[sd->packet_ver][RFIFOW(fd, 0)].pos[0]);
int attachment = MAIL_ATT_ALL;
#else
uint16 packet_id = RFIFOW(fd, 0);
int mail_id = (int)RFIFOQ(fd, 2);
//int openType = RFIFOB(fd, 10);
int attachment = packet_id == 0x9f1 ? MAIL_ATT_ZENY : packet_id == 0x9f3 ? MAIL_ATT_ITEM : MAIL_ATT_NONE;
#endif
if( !chrif_isconnected() )
return;
@ -15013,54 +15416,89 @@ void clif_parse_Mail_getattach(int fd, struct map_session_data *sd)
if( i == MAIL_MAX_INBOX )
return;
if( sd->mail.inbox.msg[i].zeny < 1 && (sd->mail.inbox.msg[i].item.nameid < 1 || sd->mail.inbox.msg[i].item.amount < 1) )
return;
msg = &sd->mail.inbox.msg[i];
if( sd->mail.inbox.msg[i].zeny + sd->status.zeny > MAX_ZENY ) {
clif_Mail_getattachment(fd, 1); //too many zeny
return;
if( attachment&MAIL_ATT_ZENY && msg->zeny < 1 ){
attachment &= ~MAIL_ATT_ZENY;
}
if( attachment&MAIL_ATT_ITEM ){
ARR_FIND(0, MAIL_MAX_ITEM, i, msg->item[i].nameid > 0 || msg->item[i].amount > 0);
if( sd->mail.inbox.msg[i].item.nameid > 0 ) {
struct item_data *data;
unsigned int weight;
if ((data = itemdb_exists(sd->mail.inbox.msg[i].item.nameid)) == NULL)
return;
switch( pc_checkadditem(sd, data->nameid, sd->mail.inbox.msg[i].item.amount) ) {
case CHKADDITEM_NEW:
fail = ( pc_inventoryblank(sd) == 0 );
break;
case CHKADDITEM_OVERAMOUNT:
fail = true;
}
if( fail ) {
clif_Mail_getattachment(fd, 1);
return;
}
weight = data->weight * sd->mail.inbox.msg[i].item.amount;
if( sd->weight + weight > sd->max_weight ) {
clif_Mail_getattachment(fd, 2);
return;
// No items were found
if( i == MAIL_MAX_ITEM ){
attachment &= ~MAIL_ATT_ITEM;
}
}
sd->mail.inbox.msg[i].zeny = 0;
memset(&sd->mail.inbox.msg[i].item, 0, sizeof(struct item));
// Either no attachment requested at all or there are no zeny or items in the mail
if( attachment == MAIL_ATT_NONE ){
return;
}
if( attachment&MAIL_ATT_ZENY ){
if( msg->zeny + sd->status.zeny > MAX_ZENY ){
clif_mail_getattachment(sd, msg, 1, MAIL_ATT_ZENY); //too many zeny
return;
}else{
// To make sure another request fails
msg->zeny = 0;
}
}
if( attachment&MAIL_ATT_ITEM ){
int new_ = 0, totalweight = 0;
for( i = 0; i < MAIL_MAX_ITEM; i++ ){
struct item* item = &msg->item[i];
if( item->nameid > 0 && item->amount > 0 ){
struct item_data *data;
if((data = itemdb_exists(item->nameid)) == NULL)
continue;
switch( pc_checkadditem(sd, item->nameid, item->amount) ){
case CHKADDITEM_NEW:
new_++;
break;
case CHKADDITEM_OVERAMOUNT:
clif_mail_getattachment(sd, msg, 2, MAIL_ATT_ITEM);
return;
}
totalweight += data->weight * item->amount;
}
}
if( ( totalweight + sd->weight ) > sd->max_weight ){
clif_mail_getattachment(sd, msg, 2, MAIL_ATT_ITEM);
return;
}else if( pc_inventoryblank(sd) < new_ ){
clif_mail_getattachment(sd, msg, 2, MAIL_ATT_ITEM);
return;
}
// To make sure another request fails
memset(msg->item, 0, MAIL_MAX_ITEM*sizeof(struct item));
}
intif_mail_getattach(sd,msg,attachment);
clif_Mail_read(sd, mail_id);
intif_Mail_getattach(sd->status.char_id, mail_id);
}
/// Request to delete a mail (CZ_MAIL_DELETE).
/// 0243 <mail id>.L
/// Request to delete a mail.
/// 0243 <mail id>.L (CZ_MAIL_DELETE)
/// 09f5 <mail tab>.B <mail id>.Q (CZ_REQ_DELETE_MAIL)
void clif_parse_Mail_delete(int fd, struct map_session_data *sd){
#if PACKETVER < 20150513
int mail_id = RFIFOL(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]);
int i;
#else
int openType = RFIFOB(fd, 2);
int mail_id = (int)RFIFOQ(fd, 3);
#endif
int i, j;
if( !chrif_isconnected() )
return;
@ -15073,12 +15511,24 @@ void clif_parse_Mail_delete(int fd, struct map_session_data *sd){
if (i < MAIL_MAX_INBOX) {
struct mail_message *msg = &sd->mail.inbox.msg[i];
if( (msg->item.nameid > 0 && msg->item.amount > 0) || msg->zeny > 0 ) {// can't delete mail without removing attachment first
clif_Mail_delete(sd->fd, mail_id, 1);
// can't delete mail without removing zeny first
if( msg->zeny > 0 ){
clif_mail_delete(sd, msg, false);
return;
}
intif_Mail_delete(sd->status.char_id, mail_id);
// can't delete mail without removing attachment(s) first
for( j = 0; j < MAIL_MAX_ITEM; j++ ){
if( msg->item[j].nameid > 0 && msg->item[j].amount > 0 ){
clif_mail_delete(sd, msg, false);
return;
}
}
if( intif_Mail_delete(sd->status.char_id, mail_id) && msg->status == MAIL_UNREAD ){
sd->mail.inbox.unread--;
clif_Mail_new(sd,0,NULL,NULL);
}
}
}
@ -15103,12 +15553,17 @@ void clif_parse_Mail_return(int fd, struct map_session_data *sd){
}
/// Request to add an item or Zeny to mail (CZ_MAIL_ADD_ITEM).
/// 0247 <index>.W <amount>.L
/// Request to add an item or Zeny to mail.
/// 0247 <index>.W <amount>.L (CZ_MAIL_ADD_ITEM)
/// 0a04 <index>.W <amount>.W (CZ_REQ_ADD_ITEM_TO_MAIL)
void clif_parse_Mail_setattach(int fd, struct map_session_data *sd){
struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)];
int idx = RFIFOW(fd,info->pos[0]);
#if PACKETVER < 20150513
int amount = RFIFOL(fd,info->pos[1]);
#else
int amount = RFIFOW(fd,info->pos[1]);
#endif
unsigned char flag;
if( !chrif_isconnected() )
@ -15117,31 +15572,54 @@ void clif_parse_Mail_setattach(int fd, struct map_session_data *sd){
return;
flag = mail_setitem(sd, idx, amount);
clif_Mail_setattachment(fd,idx,!flag);
clif_Mail_setattachment(sd,idx,amount,flag);
}
/// Remove an item from a mail
/// 0a07 <result>.B <index>.W <amount>.W <weight>.W
void clif_mail_removeitem( struct map_session_data* sd, bool success, int index, int amount ){
int fd = sd->fd;
/// Request to reset mail item and/or Zeny (CZ_MAIL_RESET_ITEM).
/// 0246 <type>.W
WFIFOHEAD(fd, 9);
WFIFOW(fd, 0) = 0xa07;
WFIFOB(fd, 2) = success;
WFIFOW(fd, 3) = index;
WFIFOW(fd, 5) = amount;
WFIFOW(fd, 7) = 0; // TODO: which weight? item weight? removed weight? remaining weight?
WFIFOSET(fd, 9);
}
/// Request to reset mail item and/or Zeny
/// 0246 <type>.W (CZ_MAIL_RESET_ITEM)
/// type:
/// 0 = reset all
/// 1 = remove item
/// 2 = remove zeny
/// 0a06 <index>.W <amount>.W (CZ_REQ_REMOVE_ITEM_MAIL)
void clif_parse_Mail_winopen(int fd, struct map_session_data *sd)
{
#if PACKETVER < 20150513
int type = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]);
if (type == 0 || type == 1)
mail_removeitem(sd, 0);
mail_removeitem(sd, 0, sd->mail.item[0].index, sd->mail.item[0].amount);
if (type == 0 || type == 2)
mail_removezeny(sd, 0);
mail_removezeny(sd, false);
#else
uint16 index = RFIFOW(fd, 2);
uint16 count = RFIFOW(fd, 4);
mail_removeitem(sd,0,index,count);
#endif
}
/// Request to send mail (CZ_MAIL_SEND).
/// 0248 <packet len>.W <recipient>.24B <title>.40B <body len>.B <body>.?B
void clif_parse_Mail_send(int fd, struct map_session_data *sd)
{
/// Request to send mail
/// 0248 <packet len>.W <recipient>.24B <title>.40B <body len>.B <body>.?B (CZ_MAIL_SEND)
/// 09ec <packet len>.W <recipient>.24B <sender>.24B <zeny>.Q <title length>.W <body length>.W <title>.?B <body>.?B (CZ_REQ_WRITE_MAIL)
/// 0a6e <packet len>.W <recipient>.24B <sender>.24B <zeny>.Q <title length>.W <body length>.W <char id>.L <title>.?B <body>.?B (CZ_REQ_WRITE_MAIL2)
void clif_parse_Mail_send(int fd, struct map_session_data *sd){
#if PACKETVER < 20150513
struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)];
if( !chrif_isconnected() )
@ -15153,6 +15631,59 @@ void clif_parse_Mail_send(int fd, struct map_session_data *sd)
}
mail_send(sd, RFIFOCP(fd,info->pos[1]), RFIFOCP(fd,info->pos[2]), RFIFOCP(fd,info->pos[4]), RFIFOB(fd,info->pos[3]));
#else
unsigned short length;
static char receiver[NAME_LENGTH];
static char sender[NAME_LENGTH];
char *title;
char *text;
uint64 zeny;
uint16 titleLength;
uint16 textLength;
length = RFIFOW(fd, 2);
if( length < 0x3e ){
ShowWarning("Too short...\n");
clif_Mail_send(sd, WRITE_MAIL_FAILED);
return;
}
// Forged request without a begin writing packet?
if( !sd->state.mail_writing ){
return; // Ignore it
}
safestrncpy(receiver, RFIFOCP(fd, 4), NAME_LENGTH);
safestrncpy(sender, RFIFOCP(fd, 28), NAME_LENGTH);
zeny = RFIFOQ(fd, 52);
titleLength = RFIFOW(fd, 60);
textLength = RFIFOW(fd, 62);
title = (char*)aMalloc(titleLength);
text = (char*)aMalloc(textLength);
#if PACKETVER <= 20160330
safestrncpy(title, RFIFOCP(fd, 64), titleLength);
safestrncpy(text, RFIFOCP(fd, 64 + titleLength), textLength);
#else
// 64 = <char id>.L
safestrncpy(title, RFIFOCP(fd, 68), titleLength);
safestrncpy(text, RFIFOCP(fd, 68 + titleLength), textLength);
#endif
if( zeny > 0 ){
if( mail_setitem(sd,0,(uint32)zeny) != MAIL_ATTACH_SUCCESS ){
clif_Mail_send(sd,WRITE_MAIL_FAILED);
return;
}
}
mail_send(sd, receiver, title, text, textLength);
aFree(title);
aFree(text);
#endif
}
@ -19661,20 +20192,20 @@ void packetdb_readdb(bool reload)
0, 0, 0, 8, 8, 0, 0, 0, 0, 0, 23, 17, 0, 0,102, 0,
0, 0, 0, 0, 2, 0, -1, -1, 2, 0, 0, -1, -1, -1, 0, 7,
0, 0, 0, 0, 0, 18, 22, 3, 11, 0, 11, -1, 0, 3, 11, 0,
0, 11, 12, 11, 0, 0, 0, 75, -1,143, 0, 0, 0, -1, -1, -1,
-1, 11, 12, 11, 0, 0, 0, 75, -1,143, 0, 0, 0, -1, -1, -1,
//#0x0A00
#if PACKETVER >= 20141022
269, 3, 4, 2, 6, 49, 6, 9, 26, 45, 47, 47, 56, -1, 14, -1,
#else
269, 0, 0, 2, 6, 48, 6, 9, 26, 45, 47, 47, 56, -1, 14, 0,
#endif
-1, 0, 0, 26, 0, 0, 0, 0, 14, 2, 23, 2, -1, 2, 3, 2,
-1, 0, 0, 26, 10, 0, 0, 0, 14, 2, 23, 2, -1, 2, 3, 2,
21, 3, 5, 0, 66, 0, 0, 8, 3, 0, 0, -1, 0, -1, 0, 0,
106, 0, 0, 0, 0, 4, 0, 59, 0, 0, 0, 0, 0, 0, 0, 0,
//#0x0A40
0, 0, 0, 85, -1, 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, 34, 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, -1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
//#0x0A80
0, 0, 0, 0, 94, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@ -19851,6 +20382,9 @@ void packetdb_readdb(bool reload)
{clif_parse_Mail_setattach,"mailsetattach"},
{clif_parse_Mail_winopen,"mailwinopen"},
{clif_parse_Mail_send,"mailsend"},
{clif_parse_Mail_beginwrite,"mailbegin"},
{clif_parse_Mail_cancelwrite,"mailcancel"},
{clif_parse_Mail_Receiver_Check,"mailreceiver"},
// AUCTION SYSTEM
{clif_parse_Auction_search,"auctionsearch"},
{clif_parse_Auction_buysell,"auctionbuysell"},

View File

@ -866,14 +866,25 @@ void do_init_clif(void);
void do_final_clif(void);
// MAIL SYSTEM
enum mail_send_result{
WRITE_MAIL_SUCCESS = 0x0,
WRITE_MAIL_FAILED = 0x1,
WRITE_MAIL_FAILED_CNT = 0x2,
WRITE_MAIL_FAILED_ITEM = 0x3,
WRITE_MAIL_FAILED_CHECK_CHARACTER_NAME = 0x4,
WRITE_MAIL_FAILED_WHISPEREXREGISTER = 0x5,
};
void clif_Mail_window(int fd, int flag);
void clif_Mail_read(struct map_session_data *sd, int mail_id);
void clif_Mail_delete(int fd, int mail_id, short fail);
void clif_mail_delete(struct map_session_data* sd, struct mail_message *msg, bool success);
void clif_Mail_return(int fd, int mail_id, short fail);
void clif_Mail_send(int fd, bool fail);
void clif_Mail_new(int fd, int mail_id, const char *sender, const char *title);
void clif_Mail_refreshinbox(struct map_session_data *sd);
void clif_Mail_getattachment(int fd, uint8 flag);
void clif_Mail_send(struct map_session_data* sd, enum mail_send_result result);
void clif_Mail_new(struct map_session_data* sd, int mail_id, const char *sender, const char *title);
void clif_Mail_refreshinbox(struct map_session_data *sd,enum mail_inbox_type type,int64 mailID);
void clif_mail_getattachment(struct map_session_data* sd, struct mail_message *msg, uint8 result, enum mail_attachment_type type);
void clif_Mail_Receiver_Ack(struct map_session_data* sd, uint32 char_id, short class_, uint32 level, const char* name);
void clif_mail_removeitem(struct map_session_data* sd, bool success, int index, int amount);
// AUCTION SYSTEM
void clif_Auction_openwindow(struct map_session_data *sd);
void clif_Auction_results(struct map_session_data *sd, short count, short pages, uint8 *buf);

View File

@ -52,6 +52,7 @@ int date_get_year(void);
enum e_month date_get_month(void);
int date_get_dayofmonth(void);
enum e_dayofweek date_get_dayofweek(void);
int date_get_dayofyear(void);
int date_get_day(void);
int date_get_hour(void);
int date_get_min(void);

View File

@ -32,7 +32,7 @@ static const int packet_len_table[] = {
0, 0, 0, 0, 0, 0, 0, 0, -1,11, 0, 0, 0, 0, 0, 0, //0x3810
39,-1,15,15, 15+NAME_LENGTH,19, 7,-1, 0, 0, 0, 0, 0, 0, 0, 0, //0x3820
10,-1,15, 0, 79,19, 7,-1, 0,-1,-1,-1, 14,67,186,-1, //0x3830
-1, 0, 0,14, 0, 0, 0, 0, -1,74,-1,11, 11,-1, 0, 0, //0x3840
-1, 0, 0,14, 0, 0, 0, 0, -1,75,-1,11, 11,-1, 38, 0, //0x3840
-1,-1, 7, 7, 7,11, 8,-1, 0, 0, 0, 0, 0, 0, 0, 0, //0x3850 Auctions [Zephyrus] itembound[Akinari]
-1, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3860 Quests [Kevin] [Inkfish]
-1, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 3, 3, 0, //0x3870 Mercenaries [Zephyrus] / Elemental [pakpil]
@ -2087,16 +2087,17 @@ int intif_quest_save(struct map_session_data *sd)
* @param flag 0 Update Inbox | 1 OpenMail
* @return 0=errur, 1=msg_sent
*/
int intif_Mail_requestinbox(uint32 char_id, unsigned char flag)
int intif_Mail_requestinbox(uint32 char_id, unsigned char flag, enum mail_inbox_type type)
{
if (CheckForCharServer())
return 0;
WFIFOHEAD(inter_fd,7);
WFIFOHEAD(inter_fd,8);
WFIFOW(inter_fd,0) = 0x3048;
WFIFOL(inter_fd,2) = char_id;
WFIFOB(inter_fd,6) = flag;
WFIFOSET(inter_fd,7);
WFIFOB(inter_fd,7) = type;
WFIFOSET(inter_fd,8);
return 1;
}
@ -2120,24 +2121,29 @@ int intif_parse_Mail_inboxreceived(int fd)
return 0;
}
if (RFIFOW(fd,2) - 9 != sizeof(struct mail_data))
if (RFIFOW(fd,2) - 10 != sizeof(struct mail_data))
{
ShowError("intif_parse_Mail_inboxreceived: data size error %d %d\n", RFIFOW(fd,2) - 9, sizeof(struct mail_data));
ShowError("intif_parse_Mail_inboxreceived: data size error %d %d\n", RFIFOW(fd,2) - 10, sizeof(struct mail_data));
return 0;
}
//FIXME: this operation is not safe [ultramage]
memcpy(&sd->mail.inbox, RFIFOP(fd,9), sizeof(struct mail_data));
memcpy(&sd->mail.inbox, RFIFOP(fd,10), sizeof(struct mail_data));
sd->mail.changed = false; // cache is now in sync
if (flag)
clif_Mail_refreshinbox(sd);
else if( battle_config.mail_show_status && ( battle_config.mail_show_status == 1 || sd->mail.inbox.unread ) )
if (flag){
#if PACKETVER >= 20150513
// Refresh top right icon
clif_Mail_new(sd, 0, NULL, NULL);
#endif
clif_Mail_refreshinbox(sd,RFIFOB(fd,9),0);
}else if( battle_config.mail_show_status && ( battle_config.mail_show_status == 1 || sd->mail.inbox.unread ) )
{
char output[128];
sprintf(output, msg_txt(sd,510), sd->mail.inbox.unchecked, sd->mail.inbox.unread + sd->mail.inbox.unchecked);
clif_messagecolor(&sd->bl, color_table[COLOR_LIGHT_GREEN], output, false, SELF);
}
return 1;
}
@ -2165,18 +2171,18 @@ int intif_Mail_read(int mail_id)
* @param mail_id : Mail identification
* @return 0=error, 1=msg sent
*/
int intif_Mail_getattach(uint32 char_id, int mail_id)
{
bool intif_mail_getattach( struct map_session_data* sd, struct mail_message *msg, enum mail_attachment_type type){
if (CheckForCharServer())
return 0;
return false;
WFIFOHEAD(inter_fd,10);
WFIFOHEAD(inter_fd,11);
WFIFOW(inter_fd,0) = 0x304a;
WFIFOL(inter_fd,2) = char_id;
WFIFOL(inter_fd,6) = mail_id;
WFIFOSET(inter_fd, 10);
WFIFOL(inter_fd,2) = sd->status.char_id;
WFIFOL(inter_fd,6) = msg->id;
WFIFOB(inter_fd,10) = (uint8)type;
WFIFOSET(inter_fd, 11);
return 1;
return true;
}
/**
@ -2187,8 +2193,14 @@ int intif_Mail_getattach(uint32 char_id, int mail_id)
int intif_parse_Mail_getattach(int fd)
{
struct map_session_data *sd;
struct item item;
int zeny = RFIFOL(fd,8);
struct item item[MAIL_MAX_ITEM];
int i, mail_id, zeny;
if (RFIFOW(fd, 2) - 16 != sizeof(struct item)*MAIL_MAX_ITEM)
{
ShowError("intif_parse_Mail_getattach: data size error %d %d\n", RFIFOW(fd, 2) - 16, sizeof(struct item));
return 0;
}
sd = map_charid2sd( RFIFOL(fd,4) );
@ -2198,15 +2210,17 @@ int intif_parse_Mail_getattach(int fd)
return 0;
}
if (RFIFOW(fd,2) - 12 != sizeof(struct item))
{
ShowError("intif_parse_Mail_getattach: data size error %d %d\n", RFIFOW(fd,2) - 16, sizeof(struct item));
mail_id = RFIFOL(fd, 8);
ARR_FIND(0, MAIL_MAX_INBOX, i, sd->mail.inbox.msg[i].id == mail_id);
if (i == MAIL_MAX_INBOX)
return 0;
}
memcpy(&item, RFIFOP(fd,12), sizeof(struct item));
zeny = RFIFOL(fd, 12);
mail_getattachment(sd, zeny, &item);
memcpy(item, RFIFOP(fd,16), sizeof(struct item)*MAIL_MAX_ITEM);
mail_getattachment(sd, &sd->mail.inbox.msg[i], zeny, item);
return 1;
}
@ -2240,6 +2254,7 @@ int intif_parse_Mail_delete(int fd)
uint32 char_id = RFIFOL(fd,2);
int mail_id = RFIFOL(fd,6);
bool failed = RFIFOB(fd,10);
enum mail_inbox_type type;
struct map_session_data *sd = map_charid2sd(char_id);
if (sd == NULL)
@ -2254,15 +2269,16 @@ int intif_parse_Mail_delete(int fd)
ARR_FIND(0, MAIL_MAX_INBOX, i, sd->mail.inbox.msg[i].id == mail_id);
if( i < MAIL_MAX_INBOX )
{
clif_mail_delete(sd, &sd->mail.inbox.msg[i], !failed);
type = sd->mail.inbox.msg[i].type;
memset(&sd->mail.inbox.msg[i], 0, sizeof(struct mail_message));
sd->mail.inbox.amount--;
}
if( sd->mail.inbox.full )
intif_Mail_requestinbox(sd->status.char_id, 1); // Free space is available for new mails
if( sd->mail.inbox.full || sd->mail.inbox.unchecked > 0 )
intif_Mail_requestinbox(sd->status.char_id, 1, type); // Free space is available for new mails
}
clif_Mail_delete(sd->fd, mail_id, failed);
return 1;
}
@ -2300,6 +2316,7 @@ int intif_parse_Mail_return(int fd)
struct map_session_data *sd = map_charid2sd(RFIFOL(fd,2));
int mail_id = RFIFOL(fd,6);
short fail = RFIFOB(fd,10);
enum mail_inbox_type type;
if( sd == NULL )
{
@ -2313,12 +2330,13 @@ int intif_parse_Mail_return(int fd)
ARR_FIND(0, MAIL_MAX_INBOX, i, sd->mail.inbox.msg[i].id == mail_id);
if( i < MAIL_MAX_INBOX )
{
type = sd->mail.inbox.msg[i].type;
memset(&sd->mail.inbox.msg[i], 0, sizeof(struct mail_message));
sd->mail.inbox.amount--;
}
if( sd->mail.inbox.full )
intif_Mail_requestinbox(sd->status.char_id, 1); // Free space is available for new mails
intif_Mail_requestinbox(sd->status.char_id, 1, type); // Free space is available for new mails
}
clif_Mail_return(sd->fd, mail_id, fail);
@ -2379,7 +2397,7 @@ static void intif_parse_Mail_send(int fd)
mail_deliveryfail(sd, &msg);
else
{
clif_Mail_send(sd->fd, false);
clif_Mail_send(sd, WRITE_MAIL_SUCCESS);
if( save_settings&CHARSAVE_MAIL )
chrif_save(sd, CSAVE_INVENTORY);
}
@ -2399,9 +2417,47 @@ static void intif_parse_Mail_new(int fd)
if( sd == NULL )
return;
sd->mail.changed = true;
clif_Mail_new(sd->fd, mail_id, sender_name, title);
sd->mail.inbox.unread++;
clif_Mail_new(sd, mail_id, sender_name, title);
#if PACKETVER >= 20150513
// Make sure the window gets refreshed when its open
intif_Mail_requestinbox(sd->status.char_id, 1, RFIFOB(fd,74));
#endif
}
static void intif_parse_Mail_receiver( int fd ){
struct map_session_data *sd;
sd = map_charid2sd( RFIFOL( fd, 2 ) );
// Only f the player is online
if( sd ){
clif_Mail_Receiver_Ack( sd, RFIFOL( fd, 6 ), RFIFOW( fd, 10 ), RFIFOW( fd, 12 ), RFIFOCP( fd, 14 ) );
}
}
bool intif_mail_checkreceiver( struct map_session_data* sd, char* name ){
struct map_session_data *tsd;
tsd = map_nick2sd( name, false );
// If the target player is online on this map-server
if( tsd != NULL ){
clif_Mail_Receiver_Ack( sd, tsd->status.char_id, tsd->status.class_, tsd->status.base_level, name );
return true;
}
if( CheckForCharServer() )
return false;
WFIFOHEAD(inter_fd, 6 + NAME_LENGTH);
WFIFOW(inter_fd, 0) = 0x304e;
WFIFOL(inter_fd, 2) = sd->status.char_id;
safestrncpy(WFIFOCP(inter_fd, 6), name, NAME_LENGTH);
WFIFOSET(inter_fd, 6 + NAME_LENGTH);
return true;
}
/*==========================================
@ -3525,6 +3581,7 @@ int intif_parse(int fd)
case 0x384b: intif_parse_Mail_delete(fd); break;
case 0x384c: intif_parse_Mail_return(fd); break;
case 0x384d: intif_parse_Mail_send(fd); break;
case 0x384e: intif_parse_Mail_receiver(fd); break;
// Auction System
case 0x3850: intif_parse_Auction_results(fd); break;

View File

@ -89,12 +89,13 @@ int intif_mercenary_delete(int merc_id);
int intif_mercenary_save(struct s_mercenary *merc);
// MAIL SYSTEM
int intif_Mail_requestinbox(uint32 char_id, unsigned char flag);
int intif_Mail_requestinbox(uint32 char_id, unsigned char flag, enum mail_inbox_type type);
int intif_Mail_read(int mail_id);
int intif_Mail_getattach(uint32 char_id, int mail_id);
bool intif_mail_getattach( struct map_session_data* sd, struct mail_message *msg, enum mail_attachment_type type );
int intif_Mail_delete(uint32 char_id, int mail_id);
int intif_Mail_return(uint32 char_id, int mail_id);
int intif_Mail_send(uint32 account_id, struct mail_message *msg);
bool intif_mail_checkreceiver(struct map_session_data* sd, char* name);
// AUCTION SYSTEM
int intif_Auction_requestlist(uint32 char_id, short type, int price, const char* searchtext, short page);
int intif_Auction_register(struct auction_data *auction);

View File

@ -11,48 +11,98 @@
#include "clif.h"
#include "pc.h"
#include "intif.h"
#include "date.h" // date_get_dayofyear
void mail_clear(struct map_session_data *sd)
{
sd->mail.nameid = 0;
sd->mail.index = 0;
sd->mail.amount = 0;
int i;
for( i = 0; i < MAIL_MAX_ITEM; i++ ){
sd->mail.item[i].nameid = 0;
sd->mail.item[i].index = 0;
sd->mail.item[i].amount = 0;
}
sd->mail.zeny = 0;
return;
}
int mail_removeitem(struct map_session_data *sd, short flag)
int mail_removeitem(struct map_session_data *sd, short flag, int idx, int amount)
{
int i;
nullpo_ret(sd);
if( sd->mail.amount )
{
if (flag) // Item send
pc_delitem(sd, sd->mail.index, sd->mail.amount, 1, 0, LOG_TYPE_MAIL);
else
clif_additem(sd, sd->mail.index, sd->mail.amount, 0);
idx -= 2;
if( idx < 0 || idx >= MAX_INVENTORY )
return false;
if( amount <= 0 || amount > sd->inventory.u.items_inventory[idx].amount )
return false;
ARR_FIND(0, MAIL_MAX_ITEM, i, sd->mail.item[i].index == idx && sd->mail.item[i].nameid > 0);
if( i == MAIL_MAX_ITEM ){
return false;
}
if( flag ){
if( battle_config.mail_attachment_price > 0 ){
if( pc_payzeny( sd, battle_config.mail_attachment_price, LOG_TYPE_MAIL, NULL ) ){
return false;
}
}
#if PACKETVER < 20150513
// With client update packet
pc_delitem(sd, idx, amount, 1, 0, LOG_TYPE_MAIL);
#else
// RODEX refreshes the client inventory from the ACK packet
pc_delitem(sd, idx, amount, 0, 0, LOG_TYPE_MAIL);
#endif
}else{
for( ; i < MAIL_MAX_ITEM-1; i++ ){
if (sd->mail.item[i + 1].nameid == 0)
break;
sd->mail.item[i].index = sd->mail.item[i+1].index;
sd->mail.item[i].nameid = sd->mail.item[i+1].nameid;
sd->mail.item[i].amount = sd->mail.item[i+1].amount;
}
for( ; i < MAIL_MAX_ITEM; i++ ){
sd->mail.item[i].index = 0;
sd->mail.item[i].nameid = 0;
sd->mail.item[i].amount = 0;
}
#if PACKETVER < 20150513
clif_additem(sd, idx, amount, 0);
#else
clif_mail_removeitem(sd, true, idx + 2, amount);
#endif
}
sd->mail.nameid = 0;
sd->mail.index = 0;
sd->mail.amount = 0;
return 1;
}
int mail_removezeny(struct map_session_data *sd, short flag)
{
nullpo_ret(sd);
bool mail_removezeny( struct map_session_data *sd, bool flag ){
nullpo_retr( false, sd );
if (flag && sd->mail.zeny > 0)
{ //Zeny send
pc_payzeny(sd,sd->mail.zeny,LOG_TYPE_MAIL, NULL);
if( sd->mail.zeny > 0 ){
//Zeny send
if( flag ){
if( pc_payzeny( sd, sd->mail.zeny + sd->mail.zeny * battle_config.mail_zeny_fee / 100, LOG_TYPE_MAIL, NULL ) ){
return false;
}
}else{
// Update is called by pc_payzeny, so only call it in the else condition
clif_updatestatus(sd, SP_ZENY);
}
}
if (sd->mail.zeny > 0)
clif_updatestatus(sd, SP_ZENY);
sd->mail.zeny = 0;
return 1;
return true;
}
/**
@ -60,92 +110,175 @@ int mail_removezeny(struct map_session_data *sd, short flag)
* @param sd : player attaching the content
* @param idx 0 - Zeny; >= 2 - Inventory item
* @param amount : amout of zeny or number of item
* @return True if item/zeny can be set, False if failed
* @return see enum mail_attach_result in mail.h
*/
bool mail_setitem(struct map_session_data *sd, short idx, uint32 amount) {
enum mail_attach_result mail_setitem(struct map_session_data *sd, short idx, uint32 amount) {
if( pc_istrading(sd) )
return false;
return MAIL_ATTACH_ERROR;
if( idx == 0 ) { // Zeny Transfer
if( !pc_can_give_items(sd) )
return false;
return MAIL_ATTACH_UNTRADEABLE;
#if PACKETVER < 20150513
if( amount > sd->status.zeny )
amount = sd->status.zeny;
amount = sd->status.zeny; // TODO: confirm this behavior for old mail system
#else
if( ( amount + battle_config.mail_zeny_fee / 100 * amount ) > sd->status.zeny )
return MAIL_ATTACH_ERROR;
#endif
sd->mail.zeny = amount;
// clif_updatestatus(sd, SP_ZENY);
return true;
return MAIL_ATTACH_SUCCESS;
} else { // Item Transfer
int i, j, total = 0;
idx -= 2;
mail_removeitem(sd, 0);
if( idx < 0 || idx >= MAX_INVENTORY )
return false;
return MAIL_ATTACH_ERROR;
#if PACKETVER < 20150513
i = 0;
// Remove existing item
mail_removeitem(sd, 0, sd->mail.item[i].index + 2, sd->mail.item[i].amount);
#else
ARR_FIND(0, MAIL_MAX_ITEM, i, sd->mail.item[i].index == idx && sd->mail.item[i].nameid > 0 );
// The same item had already been added to the mail
if( i < MAIL_MAX_ITEM ){
// Check if it is stackable
if( !itemdb_isstackable(sd->mail.item[i].nameid) ){
return MAIL_ATTACH_ERROR;
}
// Check if it exceeds the total amount
if( ( amount + sd->mail.item[i].amount ) > sd->inventory.u.items_inventory[idx].amount ){
return MAIL_ATTACH_ERROR;
}
// Check if it exceeds the total weight
if( battle_config.mail_attachment_weight ){
for( j = 0; j < i; j++ ){
total += sd->mail.item[j].amount * ( sd->inventory_data[sd->mail.item[j].index]->weight / 10 );
}
total += amount * sd->inventory_data[idx]->weight / 10;
if( total > battle_config.mail_attachment_weight ){
return MAIL_ATTACH_WEIGHT;
}
}
sd->mail.item[i].amount += amount;
return MAIL_ATTACH_SUCCESS;
}else{
ARR_FIND(0, MAIL_MAX_ITEM, i, sd->mail.item[i].nameid == 0);
if( i == MAIL_MAX_ITEM ){
return MAIL_ATTACH_SPACE;
}
// Check if it exceeds the total weight
if( battle_config.mail_attachment_weight ){
for( j = 0; j < i; j++ ){
total += sd->mail.item[j].amount * ( sd->inventory_data[sd->mail.item[j].index]->weight / 10 );
}
total += amount * sd->inventory_data[idx]->weight / 10;
if( total > battle_config.mail_attachment_weight ){
return MAIL_ATTACH_WEIGHT;
}
}
}
#endif
if( amount > sd->inventory.u.items_inventory[idx].amount )
return false;
return MAIL_ATTACH_ERROR;
if( !pc_can_give_items(sd) || sd->inventory.u.items_inventory[idx].expire_time
|| !itemdb_available(sd->inventory.u.items_inventory[idx].nameid)
|| !itemdb_canmail(&sd->inventory.u.items_inventory[idx],pc_get_group_level(sd))
|| (sd->inventory.u.items_inventory[idx].bound && !pc_can_give_bounded_items(sd)) )
return false;
return MAIL_ATTACH_UNTRADEABLE;
sd->mail.index = idx;
sd->mail.nameid = sd->inventory.u.items_inventory[idx].nameid;
sd->mail.amount = amount;
return true;
sd->mail.item[i].index = idx;
sd->mail.item[i].nameid = sd->inventory.u.items_inventory[idx].nameid;
sd->mail.item[i].amount = amount;
return MAIL_ATTACH_SUCCESS;
}
}
bool mail_setattachment(struct map_session_data *sd, struct mail_message *msg)
{
int n;
int i, amount;
nullpo_retr(false,sd);
nullpo_retr(false,msg);
if( sd->mail.zeny < 0 || sd->mail.zeny > sd->status.zeny )
return false;
for( i = 0, amount = 0; i < MAIL_MAX_ITEM; i++ ){
int index = sd->mail.item[i].index;
n = sd->mail.index;
if( sd->mail.amount )
{
if( sd->inventory.u.items_inventory[n].nameid != sd->mail.nameid )
if( sd->mail.item[i].nameid == 0 || sd->mail.item[i].amount == 0 ){
memset(&msg->item[i], 0x00, sizeof(struct item));
continue;
}
amount++;
if( sd->inventory.u.items_inventory[index].nameid != sd->mail.item[i].nameid )
return false;
if( sd->inventory.u.items_inventory[n].amount < sd->mail.amount )
if( sd->inventory.u.items_inventory[index].amount < sd->mail.item[i].amount )
return false;
if( sd->weight > sd->max_weight )
if( sd->weight > sd->max_weight ) // TODO: Why check something weird like this here?
return false;
memcpy(&msg->item, &sd->inventory.u.items_inventory[n], sizeof(struct item));
msg->item.amount = sd->mail.amount;
memcpy(&msg->item[i], &sd->inventory.u.items_inventory[index], sizeof(struct item));
msg->item[i].amount = sd->mail.item[i].amount;
}
else
memset(&msg->item, 0x00, sizeof(struct item));
if( sd->mail.zeny < 0 || ( sd->mail.zeny + sd->mail.zeny * battle_config.mail_zeny_fee / 100 + amount * battle_config.mail_attachment_price ) > sd->status.zeny )
return false;
msg->zeny = sd->mail.zeny;
// Removes the attachment from sender
mail_removeitem(sd,1);
mail_removezeny(sd,1);
for( i = 0; i < MAIL_MAX_ITEM; i++ ){
if( sd->mail.item[i].nameid == 0 || sd->mail.item[i].amount == 0 ){
// Exit the loop on the first empty entry
break;
}
mail_removeitem(sd,1,sd->mail.item[i].index + 2,sd->mail.item[i].amount);
}
mail_removezeny(sd,true);
return true;
}
void mail_getattachment(struct map_session_data* sd, int zeny, struct item* item)
{
if( item->nameid > 0 && item->amount > 0 )
{
pc_additem(sd, item, item->amount, LOG_TYPE_MAIL);
clif_Mail_getattachment(sd->fd, 0);
void mail_getattachment(struct map_session_data* sd, struct mail_message* msg, int zeny, struct item* item){
int i;
bool item_received = false;
for( i = 0; i < MAIL_MAX_ITEM; i++ ){
if( item->nameid > 0 && item->amount > 0 ){
pc_additem(sd, &item[i], item[i].amount, LOG_TYPE_MAIL);
item_received = true;
}
}
if( zeny > 0 )
{ //Zeny receive
if( item_received ){
clif_mail_getattachment( sd, msg, 0, MAIL_ATT_ITEM );
}
// Zeny receive
if( zeny > 0 ){
pc_getzeny(sd, zeny,LOG_TYPE_MAIL, NULL);
clif_mail_getattachment( sd, msg, 0, MAIL_ATT_ZENY );
}
}
@ -161,33 +294,37 @@ int mail_openmail(struct map_session_data *sd)
return 1;
}
void mail_deliveryfail(struct map_session_data *sd, struct mail_message *msg)
{
void mail_deliveryfail(struct map_session_data *sd, struct mail_message *msg){
int i, zeny = 0;
nullpo_retv(sd);
nullpo_retv(msg);
if( msg->item.amount > 0 )
{
// Item receive (due to failure)
pc_additem(sd, &msg->item, msg->item.amount, LOG_TYPE_MAIL);
for( i = 0; i < MAIL_MAX_ITEM; i++ ){
if( msg->item[i].amount > 0 ){
// Item receive (due to failure)
pc_additem(sd, &msg->item[i], msg->item[i].amount, LOG_TYPE_MAIL);
zeny += battle_config.mail_attachment_price;
}
}
if( msg->zeny > 0 )
{
pc_getzeny(sd,msg->zeny,LOG_TYPE_MAIL, NULL); //Zeny receive (due to failure)
if( msg->zeny > 0 ){
pc_getzeny(sd,msg->zeny + msg->zeny*battle_config.mail_zeny_fee/100 + zeny,LOG_TYPE_MAIL, NULL); //Zeny receive (due to failure)
}
clif_Mail_send(sd->fd, true);
clif_Mail_send(sd, WRITE_MAIL_FAILED);
}
// This function only check if the mail operations are valid
bool mail_invalid_operation(struct map_session_data *sd)
{
#if PACKETVER < 20150513
if( !map[sd->bl.m].flag.town && !pc_can_use_command(sd, "mail", COMMAND_ATCOMMAND) )
{
ShowWarning("clif_parse_Mail: char '%s' trying to do invalid mail operations.\n", sd->status.name);
return true;
}
#endif
return false;
}
@ -210,17 +347,33 @@ void mail_send(struct map_session_data *sd, const char *dest_name, const char *t
if( DIFF_TICK(sd->cansendmail_tick, gettick()) > 0 ) {
clif_displaymessage(sd->fd,msg_txt(sd,675)); //"Cannot send mails too fast!!."
clif_Mail_send(sd->fd, true); // fail
clif_Mail_send(sd, WRITE_MAIL_FAILED); // fail
return;
}
if( battle_config.mail_daily_count ){
mail_refresh_remaining_amount(sd);
// After calling mail_refresh_remaining_amount the status should always be there
if( sd->sc.data[SC_DAILYSENDMAILCNT] == NULL || sd->sc.data[SC_DAILYSENDMAILCNT]->val2 >= battle_config.mail_daily_count ){
clif_Mail_send(sd, WRITE_MAIL_FAILED_CNT);
return;
}else{
sc_start2( &sd->bl, &sd->bl, SC_DAILYSENDMAILCNT, 100, date_get_dayofyear(), sd->sc.data[SC_DAILYSENDMAILCNT]->val2 + 1, -1 );
}
}
if( body_len > MAIL_BODY_LENGTH )
body_len = MAIL_BODY_LENGTH;
if( !mail_setattachment(sd, &msg) ) { // Invalid Append condition
clif_Mail_send(sd->fd, true); // fail
mail_removeitem(sd,0);
mail_removezeny(sd,0);
int i;
clif_Mail_send(sd, WRITE_MAIL_FAILED); // fail
for( i = 0; i < MAIL_MAX_ITEM; i++ ){
mail_removeitem(sd,0,sd->mail.item[i].index + 2, sd->mail.item[i].amount);
}
mail_removezeny(sd,false);
return;
}
@ -230,6 +383,7 @@ void mail_send(struct map_session_data *sd, const char *dest_name, const char *t
safestrncpy(msg.send_name, sd->status.name, NAME_LENGTH);
safestrncpy(msg.dest_name, (char*)dest_name, NAME_LENGTH);
safestrncpy(msg.title, (char*)title, MAIL_TITLE_LENGTH);
msg.type = MAIL_INBOX_NORMAL;
if (msg.title[0] == '\0') {
return; // Message has no length and somehow client verification was skipped.
@ -246,3 +400,14 @@ void mail_send(struct map_session_data *sd, const char *dest_name, const char *t
sd->cansendmail_tick = gettick() + battle_config.mail_delay; // Flood Protection
}
void mail_refresh_remaining_amount( struct map_session_data* sd ){
int doy = date_get_dayofyear();
nullpo_retv(sd);
// If it was not yet started or it was started on another day
if( sd->sc.data[SC_DAILYSENDMAILCNT] == NULL || sd->sc.data[SC_DAILYSENDMAILCNT]->val1 != doy ){
sc_start2( &sd->bl, &sd->bl, SC_DAILYSENDMAILCNT, 100, doy, 0, -1 );
}
}

View File

@ -6,15 +6,31 @@
#include "../common/mmo.h"
enum mail_attach_result {
MAIL_ATTACH_SUCCESS = 0,
#if PACKETVER >= 20150513
MAIL_ATTACH_WEIGHT = 1,
MAIL_ATTACH_ERROR = 2,
MAIL_ATTACH_SPACE = 3,
MAIL_ATTACH_UNTRADEABLE = 4
#else
MAIL_ATTACH_WEIGHT = 1,
MAIL_ATTACH_ERROR = 1,
MAIL_ATTACH_SPACE = 1,
MAIL_ATTACH_UNTRADEABLE = 1
#endif
};
void mail_clear(struct map_session_data *sd);
int mail_removeitem(struct map_session_data *sd, short flag);
int mail_removezeny(struct map_session_data *sd, short flag);
bool mail_setitem(struct map_session_data *sd, short idx, uint32 amount);
int mail_removeitem(struct map_session_data *sd, short flag, int idx, int amount);
bool mail_removezeny(struct map_session_data *sd, bool flag);
enum mail_attach_result mail_setitem(struct map_session_data *sd, short idx, uint32 amount);
bool mail_setattachment(struct map_session_data *sd, struct mail_message *msg);
void mail_getattachment(struct map_session_data* sd, int zeny, struct item* item);
void mail_getattachment(struct map_session_data* sd, struct mail_message* msg, int zeny, struct item* item);
int mail_openmail(struct map_session_data *sd);
void mail_deliveryfail(struct map_session_data *sd, struct mail_message *msg);
bool mail_invalid_operation(struct map_session_data *sd);
void mail_send(struct map_session_data *sd, const char *dest_name, const char *title, const char *body_msg, int body_len);
void mail_refresh_remaining_amount( struct map_session_data* sd );
#endif /* _MAIL_H_ */

View File

@ -1449,7 +1449,7 @@ void pc_reg_received(struct map_session_data *sd)
sd->vip.enabled = 0;
chrif_req_login_operation(sd->status.account_id, sd->status.name, CHRIF_OP_LOGIN_VIP, 0, 1|8, 0); // request VIP information
#endif
intif_Mail_requestinbox(sd->status.char_id, 0); // MAIL SYSTEM - Request Mail Inbox
intif_Mail_requestinbox(sd->status.char_id, 0, MAIL_INBOX_NORMAL); // MAIL SYSTEM - Request Mail Inbox
intif_request_questlog(sd);
if (sd->state.connect_new == 0 && sd->fd) { //Character already loaded map! Gotta trigger LoadEndAck manually.

View File

@ -262,6 +262,7 @@ struct map_session_data {
unsigned int workinprogress : 2; // See clif.h::e_workinprogress
bool pc_loaded; // Ensure inventory data and status data is loaded before we calculate player stats
bool keepshop; // Whether shop data should be removed when the player disconnects
bool mail_writing; // Whether the player is currently writing a mail in RODEX or not
} state;
struct {
unsigned char no_weapon_damage, no_magic_damage, no_misc_damage;
@ -583,8 +584,11 @@ struct map_session_data {
// Mail System [Zephyrus]
struct s_mail {
unsigned short nameid;
int index, amount, zeny;
struct {
unsigned short nameid;
int index, amount;
} item[MAIL_MAX_ITEM];
int zeny;
struct mail_data inbox;
bool changed; // if true, should sync with charserver on next mailbox request
} mail;

View File

@ -23081,6 +23081,25 @@ BUILDIN_FUNC(channel_delete) {
return SCRIPT_CMD_SUCCESS;
}
BUILDIN_FUNC(unloadnpc) {
const char *name;
struct npc_data* nd;
name = script_getstr(st, 2);
nd = npc_name2id(name);
if( nd == NULL ){
ShowError( "buildin_unloadnpc: npc '%s' was not found.\n", name );
return SCRIPT_CMD_FAILURE;
}
npc_unload_duplicates(nd);
npc_unload(nd, true);
npc_read_event_script();
return SCRIPT_CMD_SUCCESS;
}
#include "../custom/script.inc"
// declarations that were supposed to be exported from npc_chat.c
@ -23682,6 +23701,7 @@ struct script_function buildin_func[] = {
BUILDIN_DEF(needed_status_point,"ii?"),
BUILDIN_DEF(jobcanentermap,"s?"),
BUILDIN_DEF(openstorage2,"ii?"),
BUILDIN_DEF(unloadnpc, "s"),
// WoE TE
BUILDIN_DEF(agitstart3,""),

View File

@ -1466,6 +1466,7 @@
export_constant(SC_ARMOR_ELEMENT_EARTH);
export_constant(SC_ARMOR_ELEMENT_FIRE);
export_constant(SC_ARMOR_ELEMENT_WIND);
export_constant(SC_DAILYSENDMAILCNT);
#ifdef RENEWAL
export_constant(SC_EXTREMITYFIST2);
#endif

View File

@ -1886,6 +1886,7 @@ int skill_additional_effect(struct block_list* src, struct block_list *bl, uint1
case SC_SPRITEMABLE: case SC_BITESCAR:
case SC_CLAN_INFO: case SC_SWORDCLAN: case SC_ARCWANDCLAN:
case SC_GOLDENMACECLAN: case SC_CROSSBOWCLAN:
case SC_DAILYSENDMAILCNT:
continue;
case SC_WHISTLE: case SC_ASSNCROS: case SC_POEMBRAGI:
case SC_APPLEIDUN: case SC_HUMMING: case SC_DONTFORGETME:
@ -7940,6 +7941,7 @@ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, ui
case SC_GOLDENMACECLAN:
case SC_CROSSBOWCLAN:
case SC_JUMPINGCLAN:
case SC_DAILYSENDMAILCNT:
continue;
case SC_WHISTLE:
case SC_ASSNCROS:
@ -9337,6 +9339,7 @@ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, ui
case SC_QUEST_BUFF1: case SC_QUEST_BUFF2: case SC_QUEST_BUFF3:
case SC_CLAN_INFO: case SC_SWORDCLAN: case SC_ARCWANDCLAN:
case SC_GOLDENMACECLAN: case SC_CROSSBOWCLAN:
case SC_DAILYSENDMAILCNT:
continue;
case SC_ASSUMPTIO:
if( bl->type == BL_MOB )

View File

@ -1116,6 +1116,9 @@ void initChangeTables(void)
StatusIconChangeTable[SC_GEFFEN_MAGIC2] = SI_GEFFEN_MAGIC2;
StatusIconChangeTable[SC_GEFFEN_MAGIC3] = SI_GEFFEN_MAGIC3;
// RODEX
StatusIconChangeTable[SC_DAILYSENDMAILCNT] = SI_DAILYSENDMAILCNT;
/* Other SC which are not necessarily associated to skills */
StatusChangeFlagTable[SC_ASPDPOTION0] |= SCB_ASPD;
StatusChangeFlagTable[SC_ASPDPOTION1] |= SCB_ASPD;
@ -1260,6 +1263,9 @@ void initChangeTables(void)
StatusChangeFlagTable[SC_CROSSBOWCLAN] |= SCB_DEX|SCB_AGI|SCB_MAXHP|SCB_MAXSP;
StatusChangeFlagTable[SC_JUMPINGCLAN] |= SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK;
// RODEX
StatusChangeFlagTable[SC_DAILYSENDMAILCNT] |= SCB_NONE;
#ifdef RENEWAL
// renewal EDP increases your weapon atk
StatusChangeFlagTable[SC_EDP] |= SCB_WATK;
@ -9158,6 +9164,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
case SC_PUSH_CART:
case SC_SPRITEMABLE:
case SC_CLAN_INFO:
case SC_DAILYSENDMAILCNT:
tick = -1;
break;
@ -10898,6 +10905,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
case SC_UPHEAVAL_OPTION:
case SC_CIRCLE_OF_FIRE_OPTION:
case SC_CLAN_INFO:
case SC_DAILYSENDMAILCNT:
val_flag |= 1|2;
break;
// Start |1|2|4 val_flag setting
@ -11512,6 +11520,7 @@ int status_change_clear(struct block_list* bl, int type)
case SC_GOLDENMACECLAN:
case SC_CROSSBOWCLAN:
case SC_JUMPINGCLAN:
case SC_DAILYSENDMAILCNT:
continue;
}
}
@ -11545,6 +11554,7 @@ int status_change_clear(struct block_list* bl, int type)
case SC_GOLDENMACECLAN:
case SC_CROSSBOWCLAN:
case SC_JUMPINGCLAN:
case SC_DAILYSENDMAILCNT:
continue;
}
}

View File

@ -798,6 +798,8 @@ typedef enum sc_type {
SC_ARMOR_ELEMENT_FIRE,
SC_ARMOR_ELEMENT_WIND,
SC_DAILYSENDMAILCNT,
#ifdef RENEWAL
SC_EXTREMITYFIST2, //! NOTE: This SC should be right before SC_MAX, so it doesn't disturb if RENEWAL is disabled
#endif