Browse Source

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.
Lemongrass3110 8 years ago
parent
commit
58776da1ac

+ 23 - 0
conf/battle/misc.conf

@@ -134,6 +134,29 @@ cashshop_show_points: no
 // 2 = Yes, when there are unread mails
 // 2 = Yes, when there are unread mails
 mail_show_status: 0
 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?
 // Is monster transformation disabled during Guild Wars?
 // If set to yes, monster transforming is automatically removed/disabled when enterting castles during WoE times
 // If set to yes, monster transforming is automatically removed/disabled when enterting castles during WoE times
 mon_trans_disable_in_gvg: no
 mon_trans_disable_in_gvg: no

+ 15 - 0
conf/char_athena.conf

@@ -261,4 +261,19 @@ default_map_y: 191
 // Default: 14
 // Default: 14
 clan_remove_inactive_days: 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
 import: conf/import/char_conf.txt

+ 1 - 0
conf/inter_athena.conf

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

+ 23 - 14
db/packet_db.txt

@@ -2497,31 +2497,31 @@ packet_keys: 0x62C86D09,0x75944F17,0x112C133D // [YomRawr]
 
 
 // RODEX Mail system
 // RODEX Mail system
 0x09E7,3		// ZC_NOTIFY_UNREADMAIL
 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
 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
 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
 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
 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
 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
 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
 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
 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
 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
 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
 0x0A14,10		// ZC_CHECK_RECEIVE_CHARACTER_NAME
+0x0A32,2		// ZC_OPEN_RODEX_THROUGH_NPC_ONLY
 
 
 // New EquipPackets Support
 // New EquipPackets Support
 0x0A09,45	// ZC_ADD_EXCHANGE_ITEM3
 0x0A09,45	// ZC_ADD_EXCHANGE_ITEM3
@@ -2657,6 +2657,15 @@ packet_keys: 0x4C17382A,0x7ED174C9,0x29961E4F // [Winnie]
 0x088D,5,hommenu,2:4
 0x088D,5,hommenu,2:4
 0x0940,36,storagepassword,2:4:20
 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
 // 2017-05-24aRagexeRE
 0x0A43,85
 0x0A43,85
 0x0A44,-1
 0x0A44,-1

+ 39 - 9
doc/packet_interserv.txt

@@ -1006,13 +1006,14 @@ Currently the max packet size is 0xFFFF (see 'WFIFOSET()' in 'src/common/socket.
 
 
 0x3048
 0x3048
 	Type: ZI
 	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:
 	parameter:
 		- cmd : packet identification (0x3048)
 		- cmd : packet identification (0x3048)
 		- cid
 		- cid
 		- flag
 		- flag
+		- mail_type
 	desc:
 	desc:
 		- Inbox request
 		- Inbox request
 
 
@@ -1029,13 +1030,14 @@ Currently the max packet size is 0xFFFF (see 'WFIFOSET()' in 'src/common/socket.
 
 
 0x304a
 0x304a
 	Type: ZI
 	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:
 	parameter:
 		- cmd : packet identification (0x304a)
 		- cmd : packet identification (0x304a)
 		- cid
 		- cid
 		- mail_id
 		- mail_id
+		- attachment_type
 	desc:
 	desc:
 		- Mail get attachment
 		- Mail get attachment
 
 
@@ -1076,6 +1078,18 @@ Currently the max packet size is 0xFFFF (see 'WFIFOSET()' in 'src/common/socket.
 	desc:
 	desc:
 		- Mail send
 		- 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
 0x3050
 	Type: ZI
 	Type: ZI
 	Structure: <cmd>.W <len>.W <cid>.L <type>.W <price>.L <page>.W <searchtext>.?B
 	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
 0x3848
 	Type: IZ
 	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:
 	parameter:
 		- cmd : packet identification (0x3848)
 		- cmd : packet identification (0x3848)
 		- size
 		- size
 		- char_id
 		- char_id
 		- flag
 		- flag
+		- mail_type
 		- md : Mail
 		- md : Mail
 	desc:
 	desc:
 		- A player request for mail inbox
 		- A player request for mail inbox
@@ -2044,6 +2059,21 @@ Currently the max packet size is 0xFFFF (see 'WFIFOSET()' in 'src/common/socket.
 	desc:
 	desc:
 		- Mail sent status (to player if the sender is player and online)
 		- 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
 0x3850
 	Type: IZ
 	Type: IZ
 	Structure: <cmd>.W <size>.W <char_id>.L <count>.W <pages>.W <auction_data>.?B
 	Structure: <cmd>.W <size>.W <char_id>.L <count>.W <pages>.W <auction_data>.?B

+ 6 - 0
doc/script_commands.txt

@@ -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>";
 *doevent "<NPC object name>::<event label>";
 
 
 This command will start a new execution thread in a specified NPC object at the 
 This command will start a new execution thread in a specified NPC object at the 

+ 11 - 0
npc/other/mail.txt

@@ -137,3 +137,14 @@ rachel,122,146,0	duplicate(MailBox)	Post Box#ra	888
 // Veins
 // Veins
 //============================================================
 //============================================================
 veins,218,123,0	duplicate(MailBox)	Post Box#ve	888
 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;
+}

+ 37 - 26
sql-files/main.sql

@@ -716,35 +716,46 @@ CREATE TABLE IF NOT EXISTS `mail` (
   `time` int(11) unsigned NOT NULL default '0',
   `time` int(11) unsigned NOT NULL default '0',
   `status` tinyint(2) NOT NULL default '0',
   `status` tinyint(2) NOT NULL default '0',
   `zeny` int(11) unsigned 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`)
   PRIMARY KEY  (`id`)
 ) ENGINE=MyISAM;
 ) 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`
 -- Table structure for table `mapreg`
 --
 --

+ 76 - 0
sql-files/upgrades/upgrade_20170620.sql

@@ -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';

+ 31 - 3
src/char/char.c

@@ -20,6 +20,7 @@
 #include "../common/cli.h"
 #include "../common/cli.h"
 #include "int_guild.h"
 #include "int_guild.h"
 #include "int_homun.h"
 #include "int_homun.h"
+#include "int_mail.h"
 #include "int_mercenary.h"
 #include "int_mercenary.h"
 #include "int_elemental.h"
 #include "int_elemental.h"
 #include "int_party.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) )
 	if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id`='%d'", schema_config.skill_db, char_id) )
 		Sql_ShowDebug(sql_handle);
 		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) */
 	/* delete mails (only received) */
 	if (SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `dest_id`='%d'", schema_config.mail_db, char_id))
 	if (SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `dest_id`='%d'", schema_config.mail_db, char_id))
 		Sql_ShowDebug(sql_handle);
 		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.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.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.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");
 	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
 	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
 	//checking mail_db
 	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT  `id`,`send_name`,`send_id`,`dest_name`,`dest_id`,"
 	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) ){
 			" FROM `%s` LIMIT 1;", schema_config.mail_db) ){
 		Sql_ShowDebug(sql_handle);
 		Sql_ShowDebug(sql_handle);
 		return false;
 		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
 	//checking auction_db
 	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT  `auction_id`,`seller_id`,`seller_name`,`buyer_id`,`buyer_name`,"
 	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`,"
 			"`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));
 			safestrncpy(schema_config.pet_db, w2, sizeof(schema_config.pet_db));
 		else if(!strcmpi(w1,"mail_db"))
 		else if(!strcmpi(w1,"mail_db"))
 			safestrncpy(schema_config.mail_db, w2, sizeof(schema_config.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"))
 		else if(!strcmpi(w1,"auction_db"))
 			safestrncpy(schema_config.auction_db, w2, sizeof(schema_config.auction_db));
 			safestrncpy(schema_config.auction_db, w2, sizeof(schema_config.auction_db));
 		else if(!strcmpi(w1,"friend_db"))
 		else if(!strcmpi(w1,"friend_db"))
@@ -2699,6 +2713,8 @@ void char_set_defaults(){
 	charserv_config.default_map_y = 191;
 	charserv_config.default_map_y = 191;
 
 
 	charserv_config.clan_remove_inactive_days = 14;
 	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);
 			charserv_config.default_map_y = atoi(w2);
 		} else if (strcmpi(w1, "clan_remove_inactive_days") == 0) {
 		} else if (strcmpi(w1, "clan_remove_inactive_days") == 0) {
 			charserv_config.clan_remove_inactive_days = atoi(w2);
 			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) {
 		} else if (strcmpi(w1, "import") == 0) {
 			char_config_read(w2, normal);
 			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_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
 	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
 	//check db tables
 	if(charserv_config.char_check_db && char_checkdb() == 0){
 	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");
 		ShowFatalError("char : A tables is missing in sql-server, please fix it, see (sql-files main.sql for structure) \n");

+ 3 - 0
src/char/char.h

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

+ 10 - 10
src/char/int_auction.c

@@ -154,12 +154,12 @@ static int auction_end_timer(int tid, unsigned int tick, int id, intptr_t data)
 	{
 	{
 		if( auction->buyer_id )
 		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
 			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
 		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);
 		ShowInfo("Auction End: id %u.\n", auction->auction_id);
 
 
@@ -376,7 +376,7 @@ static void mapif_parse_Auction_cancel(int fd)
 		return;
 		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);
 	auction_delete(auction);
 
 
 	mapif_Auction_cancel(fd, char_id, 0); // The auction has been canceled
 	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
 	// 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
 	// 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
 	mapif_Auction_message(auction->buyer_id, 6); // You have won the auction
 	auction_delete(auction);
 	auction_delete(auction);
 
 
@@ -456,11 +456,11 @@ static void mapif_parse_Auction_bid(int fd)
 	{ // Send Money back to the previous Buyer
 	{ // Send Money back to the previous Buyer
 		if( auction->buyer_id != char_id )
 		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
 			mapif_Auction_message(auction->buyer_id, 7); // You have failed to win the auction
 		}
 		}
 		else
 		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;
 	auction->buyer_id = char_id;
@@ -471,9 +471,9 @@ static void mapif_parse_Auction_bid(int fd)
 	{ // Automatic won the auction
 	{ // Automatic won the auction
 		mapif_Auction_bid(fd, char_id, bid - auction->buynow, 1); // You have successfully bid in 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
 		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);
 		auction_delete(auction);
 		return;
 		return;

+ 320 - 169
src/char/int_mail.c

@@ -12,87 +12,47 @@
 
 
 #include <stdlib.h>
 #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)
 static int mail_fromsql(uint32 char_id, struct mail_data* md)
 {
 {
-	int i, j;
-	struct mail_message *msg;
+	int i;
 	char *data;
 	char *data;
-	StringBuf buf;
 
 
 	memset(md, 0, sizeof(struct mail_data));
 	memset(md, 0, sizeof(struct mail_data));
 	md->amount = 0;
 	md->amount = 0;
 	md->full = false;
 	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);
 		Sql_ShowDebug(sql_handle);
+		return 0;
+	}
 
 
-	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);
-		}
+	md->full = (Sql_NumRows(sql_handle) > MAIL_MAX_INBOX);
 
 
-		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);
-		}
+	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->full = ( Sql_NumRows(sql_handle) > MAIL_MAX_INBOX );
-
 	md->amount = i;
 	md->amount = i;
 	Sql_FreeResult(sql_handle);
 	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->unchecked = 0;
 	md->unread = 0;
 	md->unread = 0;
 	for (i = 0; i < md->amount; i++)
 	for (i = 0; i < md->amount; i++)
 	{
 	{
-		msg = &md->msg[i];
+		struct mail_message *msg = &md->msg[i];
+
 		if( msg->status == MAIL_NEW )
 		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) )
 			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;
 	StringBuf buf;
 	SqlStmt* stmt;
 	SqlStmt* stmt;
-	int j;
+	int i, j;
+	bool found = false;
 
 
 	// build message save query
 	// build message save query
 	StringBuf_Init(&buf);
 	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, ")");
 	StringBuf_AppendStr(&buf, ")");
 
 
 	// prepare and execute query
 	// prepare and execute query
@@ -148,11 +94,50 @@ int mail_savemessage(struct mail_message* msg)
 	||  SQL_SUCCESS != SqlStmt_Execute(stmt) )
 	||  SQL_SUCCESS != SqlStmt_Execute(stmt) )
 	{
 	{
 		SqlStmt_ShowDebug(stmt);
 		SqlStmt_ShowDebug(stmt);
-		msg->id = 0;
+		StringBuf_Destroy(&buf);
+		return msg->id = 0;
 	} else
 	} else
 		msg->id = (int)SqlStmt_LastInsertId(stmt);
 		msg->id = (int)SqlStmt_LastInsertId(stmt);
 
 
 	SqlStmt_Free(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);
 	StringBuf_Destroy(&buf);
 
 
 	return msg->id;
 	return msg->id;
@@ -162,64 +147,80 @@ int mail_savemessage(struct mail_message* msg)
 /// Returns true if the operation succeeds (or false if it fails).
 /// Returns true if the operation succeeds (or false if it fails).
 static bool mail_loadmessage(int mail_id, struct mail_message* msg)
 static bool mail_loadmessage(int mail_id, struct mail_message* msg)
 {
 {
-	int j;
+	int i, j;
 	StringBuf buf;
 	StringBuf buf;
+	char* data;
+
+	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);
+		return false;
+	}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);
+		Sql_GetData(sql_handle, 3, &data, NULL); safestrncpy(msg->dest_name, data, NAME_LENGTH);
+		Sql_GetData(sql_handle, 4, &data, NULL); msg->dest_id = atoi(data);
+		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);
+		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->type = 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;
+		}
+
+		Sql_FreeResult(sql_handle);
+	}
 
 
 	StringBuf_Init(&buf);
 	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_AppendStr(&buf, "SELECT `amount`,`nameid`,`refine`,`attribute`,`identify`,`unique_id`,`bound`");
+	for (j = 0; j < MAX_SLOTS; j++)
 		StringBuf_Printf(&buf, ",`card%d`", j);
 		StringBuf_Printf(&buf, ",`card%d`", j);
 	for (j = 0; j < MAX_ITEM_RDM_OPT; ++j) {
 	for (j = 0; j < MAX_ITEM_RDM_OPT; ++j) {
 		StringBuf_Printf(&buf, ", `option_id%d`", j);
 		StringBuf_Printf(&buf, ", `option_id%d`", j);
 		StringBuf_Printf(&buf, ", `option_val%d`", j);
 		StringBuf_Printf(&buf, ", `option_val%d`", j);
 		StringBuf_Printf(&buf, ", `option_parm%d`", j);
 		StringBuf_Printf(&buf, ", `option_parm%d`", j);
 	}
 	}
-	StringBuf_Printf(&buf, " FROM `%s` WHERE `id` = '%d'", schema_config.mail_db, mail_id);
+	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_SUCCESS != Sql_NextRow(sql_handle) )
-	{
+	if( SQL_ERROR == Sql_Query(sql_handle, StringBuf_Value(&buf)) ){
 		Sql_ShowDebug(sql_handle);
 		Sql_ShowDebug(sql_handle);
 		Sql_FreeResult(sql_handle);
 		Sql_FreeResult(sql_handle);
 		StringBuf_Destroy(&buf);
 		StringBuf_Destroy(&buf);
 		return false;
 		return false;
 	}
 	}
-	else
-	{
-		char* data;
 
 
-		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);
-		Sql_GetData(sql_handle, 3, &data, NULL); safestrncpy(msg->dest_name, data, NAME_LENGTH);
-		Sql_GetData(sql_handle, 4, &data, NULL); msg->dest_id = atoi(data);
-		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);
-		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;
-
-		for( j = 0; j < MAX_SLOTS; j++ )
-		{
-			Sql_GetData(sql_handle,17 + j, &data, NULL);
-			msg->item.card[j] = atoi(data);
+	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, 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);
+
+		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;
 	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
  * 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;
 	struct mail_data md;
 	mail_fromsql(char_id, &md);
 	mail_fromsql(char_id, &md);
 
 
 	//FIXME: dumping the whole structure like this is unsafe [ultramage]
 	//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,0) = 0x3848;
-	WFIFOW(fd,2) = sizeof(md) + 9;
+	WFIFOW(fd,2) = sizeof(md) + 10;
 	WFIFOL(fd,4) = char_id;
 	WFIFOL(fd,4) = char_id;
 	WFIFOB(fd,8) = flag;
 	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));
 	WFIFOSET(fd,WFIFOW(fd,2));
 }
 }
 
 
 static void mapif_parse_Mail_requestinbox(int fd)
 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
  * 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);
 		Sql_ShowDebug(sql_handle);
-		StringBuf_Destroy(&buf);
-
 		return false;
 		return false;
 	}
 	}
 
 
-	StringBuf_Destroy(&buf);
 	return true;
 	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;
 	struct mail_message msg;
 
 
+	if( ( type&MAIL_ATT_ALL ) == 0 ){
+		return;
+	}
+
 	if( !mail_loadmessage(mail_id, &msg) )
 	if( !mail_loadmessage(mail_id, &msg) )
 		return;
 		return;
 
 
@@ -306,36 +360,77 @@ static void mapif_Mail_getattach(int fd, uint32 char_id, int mail_id)
 	if( msg.status != MAIL_READ )
 	if( msg.status != MAIL_READ )
 		return;
 		return;
 
 
-	if( (msg.item.nameid < 1 || msg.item.amount < 1) && msg.zeny < 1 )
-		return; // No Attachment
+	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( !mail_DeleteAttach(mail_id) )
-		return;
+	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;
+			}
+		}
+	}
 
 
-	WFIFOHEAD(fd, sizeof(struct item) + 12);
+	if( type == MAIL_ATT_NONE )
+		return; // No Attachment
+
+	WFIFOHEAD(fd, sizeof(struct item)*MAIL_MAX_ITEM + 16);
 	WFIFOW(fd,0) = 0x384a;
 	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,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));
 	WFIFOSET(fd,WFIFOW(fd,2));
 }
 }
 
 
 static void mapif_parse_Mail_getattach(int fd)
 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
  * 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;
 	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);
 	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)
 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)
 void mapif_Mail_new(struct mail_message *msg)
 {
 {
-	unsigned char buf[74];
+	unsigned char buf[75];
 
 
 	if( !msg || !msg->id )
 	if( !msg || !msg->id )
 		return;
 		return;
@@ -366,7 +461,8 @@ void mapif_Mail_new(struct mail_message *msg)
 	WBUFL(buf,6) = msg->id;
 	WBUFL(buf,6) = msg->id;
 	memcpy(WBUFP(buf,10), msg->send_name, NAME_LENGTH);
 	memcpy(WBUFP(buf,10), msg->send_name, NAME_LENGTH);
 	memcpy(WBUFP(buf,34), msg->title, MAIL_TITLE_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)
 		if( msg.dest_id != char_id)
 			return;
 			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);
 			Sql_ShowDebug(sql_handle);
 		else
 		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);
 			safestrncpy(msg.title, temp_, MAIL_TITLE_LENGTH);
 
 
 			msg.status = MAIL_NEW;
 			msg.status = MAIL_NEW;
+			msg.type = MAIL_INBOX_RETURNED;
 			msg.timestamp = time(NULL);
 			msg.timestamp = time(NULL);
 
 
 			new_mail = mail_savemessage(&msg);
 			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);
 	WFIFOHEAD(fd,11);
 	WFIFOW(fd,0) = 0x384c;
 	WFIFOW(fd,0) = 0x384c;
 	WFIFOL(fd,2) = char_id;
 	WFIFOL(fd,2) = char_id;
@@ -452,12 +555,18 @@ static void mapif_parse_Mail_send(int fd)
 	if ( SQL_SUCCESS == Sql_NextRow(sql_handle) )
 	if ( SQL_SUCCESS == Sql_NextRow(sql_handle) )
 	{
 	{
 		char *data;
 		char *data;
+#if PACKETVER < 20150513
 		Sql_GetData(sql_handle, 0, &data, NULL);
 		Sql_GetData(sql_handle, 0, &data, NULL);
 		if (atoi(data) != account_id)
 		if (atoi(data) != account_id)
 		{ // Cannot send mail to char in the same account
 		{ // Cannot send mail to char in the same account
 			Sql_GetData(sql_handle, 1, &data, NULL);
 			Sql_GetData(sql_handle, 1, &data, NULL);
 			msg.dest_id = atoi(data);
 			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);
 	Sql_FreeResult(sql_handle);
 	msg.status = MAIL_NEW;
 	msg.status = MAIL_NEW;
@@ -469,7 +578,7 @@ static void mapif_parse_Mail_send(int fd)
 	mapif_Mail_new(&msg); // notify recipient
 	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;
 	struct mail_message msg;
 	memset(&msg, 0, sizeof(struct mail_message));
 	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.title, title, MAIL_TITLE_LENGTH);
 	safestrncpy(msg.body, body, MAIL_BODY_LENGTH);
 	safestrncpy(msg.body, body, MAIL_BODY_LENGTH);
 	msg.zeny = zeny;
 	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.timestamp = time(NULL);
+	msg.type = MAIL_INBOX_NORMAL;
 
 
 	mail_savemessage(&msg);
 	mail_savemessage(&msg);
 	mapif_Mail_new(&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
  * Packets From Map Server
  *------------------------------------------*/
  *------------------------------------------*/
@@ -503,6 +653,7 @@ int inter_mail_parse_frommap(int fd)
 		case 0x304b: mapif_parse_Mail_delete(fd); break;
 		case 0x304b: mapif_parse_Mail_delete(fd); break;
 		case 0x304c: mapif_parse_Mail_return(fd); break;
 		case 0x304c: mapif_parse_Mail_return(fd); break;
 		case 0x304d: mapif_parse_Mail_send(fd); break;
 		case 0x304d: mapif_parse_Mail_send(fd); break;
+		case 0x304e: mapif_parse_Mail_receiver_check(fd); break;
 		default:
 		default:
 			return 0;
 			return 0;
 	}
 	}

+ 4 - 1
src/char/int_mail.h

@@ -4,8 +4,11 @@
 #ifndef _INT_MAIL_SQL_H_
 #ifndef _INT_MAIL_SQL_H_
 #define _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);
 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);
 int inter_mail_sql_init(void);
 void inter_mail_sql_final(void);
 void inter_mail_sql_final(void);

+ 1 - 1
src/char/inter.c

@@ -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-
 	 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,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, 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]
 	-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]
 	 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]
 	-1,10, 6,-1,  0, 0, 0, 0,  0, 0, 0, 0, -1,10,  6,-1,	// 3070-  Mercenary packets [Zephyrus], Elemental packets [pakpil]

+ 23 - 1
src/common/mmo.h

@@ -140,7 +140,14 @@
 //Mail System
 //Mail System
 #define MAIL_MAX_INBOX 30
 #define MAIL_MAX_INBOX 30
 #define MAIL_TITLE_LENGTH 40
 #define MAIL_TITLE_LENGTH 40
+#if PACKETVER < 20150513
 #define MAIL_BODY_LENGTH 200
 #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
 //Mercenary System
 #define MC_SKILLBASE 8201
 #define MC_SKILLBASE 8201
@@ -500,6 +507,19 @@ typedef enum mail_status {
 	MAIL_READ,
 	MAIL_READ,
 } mail_status;
 } 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 {
 struct mail_message {
 	int id;
 	int id;
 	uint32 send_id;                 //hold char_id of sender
 	uint32 send_id;                 //hold char_id of sender
@@ -508,12 +528,14 @@ struct mail_message {
 	char dest_name[NAME_LENGTH];    //receiver nickname
 	char dest_name[NAME_LENGTH];    //receiver nickname
 	char title[MAIL_TITLE_LENGTH];
 	char title[MAIL_TITLE_LENGTH];
 	char body[MAIL_BODY_LENGTH];
 	char body[MAIL_BODY_LENGTH];
+	int type; // enum mail_inbox_type
+	time_t scheduled_deletion;
 
 
 	mail_status status;
 	mail_status status;
 	time_t timestamp; // marks when the message was sent
 	time_t timestamp; // marks when the message was sent
 
 
 	uint32 zeny;
 	uint32 zeny;
-	struct item item;
+	struct item item[MAIL_MAX_ITEM];
 };
 };
 
 
 struct mail_data {
 struct mail_data {

+ 4 - 0
src/map/battle.c

@@ -8407,6 +8407,10 @@ static const struct _battle_data {
 	{ "dispel_song",                        &battle_config.dispel_song,                     0,      0,      1,              },
 	{ "dispel_song",                        &battle_config.dispel_song,                     0,      0,      1,              },
 	{ "guild_maprespawn_clones",			&battle_config.guild_maprespawn_clones,			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,              },
 	{ "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"
 #include "../custom/battle_config_init.inc"
 };
 };

+ 4 - 0
src/map/battle.h

@@ -618,6 +618,10 @@ extern struct Battle_Config
 	int dispel_song; //Can songs be dispelled?
 	int dispel_song; //Can songs be dispelled?
 	int guild_maprespawn_clones; // Should clones be killed by maprespawnguildid?
 	int guild_maprespawn_clones; // Should clones be killed by maprespawnguildid?
 	int hide_fav_sell;
 	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"
 #include "../custom/battle_config_struct.inc"
 } battle_config;
 } battle_config;

+ 639 - 105
src/map/clif.c

@@ -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 unsigned short clif_parse_cmd(int fd, struct map_session_data *sd);
 static bool clif_session_isValid(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.
 /** Converts item type to display it on client if necessary.
 * @param nameid: Item ID
 * @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
 * @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_reinit(sd);
 	pc_show_questinfo(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.connect_new = 0;
 	sd->state.changemap = false;
 	sd->state.changemap = false;
 }
 }
@@ -11438,6 +11453,8 @@ void clif_parse_NpcClicked(int fd,struct map_session_data *sd)
 #endif
 #endif
 	if (pc_cant_act2(sd) || sd->npc_id)
 	if (pc_cant_act2(sd) || sd->npc_id)
 		return;
 		return;
+	if( sd->state.mail_writing )
+		return;
 
 
 	bl = map_id2bl(RFIFOL(fd,info->pos[0]));
 	bl = map_id2bl(RFIFOL(fd,info->pos[0]));
 	//type = RFIFOB(fd,info->pos[1]);
 	//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))
 	if(!sd->chatID && pc_cant_act(sd))
 		return; //You can trade while in a chatroom.
 		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) {
 	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
 /// 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:
 /// result:
 ///     0 = success
 ///     0 = success
 ///     1 = failure
 ///     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));
 	WFIFOHEAD(fd,packet_len(0x255));
 	WFIFOW(fd,0) = 0x255;
 	WFIFOW(fd,0) = 0x255;
 	WFIFOW(fd,2) = index;
 	WFIFOW(fd,2) = index;
 	WFIFOB(fd,4) = flag;
 	WFIFOB(fd,4) = flag;
 	WFIFOSET(fd,packet_len(0x255));
 	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:
 /// result:
 ///     0 = success
 ///     0 = success
 ///     1 = failure
 ///     1 = failure
 ///     2 = too many items
 ///     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));
 	WFIFOHEAD(fd,packet_len(0x245));
 	WFIFOW(fd,0) = 0x245;
 	WFIFOW(fd,0) = 0x245;
 	WFIFOB(fd,2) = result;
 	WFIFOB(fd,2) = result;
 	WFIFOSET(fd,packet_len(0x245));
 	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:
 /// result:
 ///     0 = success
 ///     0 = success
 ///     1 = recipinent does not exist
 ///     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));
 	WFIFOHEAD(fd,packet_len(0x249));
 	WFIFOW(fd,0) = 0x249;
 	WFIFOW(fd,0) = 0x249;
-	WFIFOB(fd,2) = fail;
+	WFIFOB(fd,2) = result != WRITE_MAIL_SUCCESS;
 	WFIFOSET(fd,packet_len(0x249));
 	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:
 /// result:
 ///     0 = success
 ///     0 = success
 ///     1 = failure
 ///     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));
 	WFIFOHEAD(fd, packet_len(0x257));
 	WFIFOW(fd,0) = 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));
 	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));
 	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));
 	WFIFOHEAD(fd,packet_len(0x24a));
 	WFIFOW(fd,0) = 0x24a;
 	WFIFOW(fd,0) = 0x24a;
 	WFIFOL(fd,2) = mail_id;
 	WFIFOL(fd,2) = mail_id;
 	safestrncpy(WFIFOCP(fd,6), title, MAIL_TITLE_LENGTH);
 	safestrncpy(WFIFOCP(fd,6), title, MAIL_TITLE_LENGTH);
 	safestrncpy(WFIFOCP(fd,46), sender, NAME_LENGTH);
 	safestrncpy(WFIFOCP(fd,46), sender, NAME_LENGTH);
 	WFIFOSET(fd,packet_len(0x24a));
 	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).
 /// Opens/closes the mail window (ZC_MAIL_WINDOWS).
 /// 0260 <type>.L
 /// 0260 <type>.L
 /// type:
 /// type:
@@ -14859,14 +14978,17 @@ void clif_Mail_window(int fd, int flag)
 	WFIFOSET(fd,packet_len(0x260));
 	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:
 /// read:
 ///     0 = unread
 ///     0 = unread
 ///     1 = read
 ///     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;
 	int fd = sd->fd;
 	struct mail_data *md = &sd->mail.inbox;
 	struct mail_data *md = &sd->mail.inbox;
 	struct mail_message *msg;
 	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);
 		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);
 		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);
+	}
 
 
-/// Mail inbox list request (CZ_MAIL_GET_LIST).
-/// 023f
-void clif_parse_Mail_refreshinbox(int fd, struct map_session_data *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.
+/// 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;
 	struct mail_data* md = &sd->mail.inbox;
 
 
 	if( md->amount < MAIL_MAX_INBOX && (md->full || sd->mail.changed) )
 	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
 	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;
+	}
 
 
-/// Opens a mail (ZC_MAIL_REQ_OPEN).
-/// 0242 <packet len>.W <mail id>.L <title>.40B <sender>.24B <time>.L <zeny>.L
+	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
+/// 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
 ///     <amount>.L <name id>.W <item type>.W <identified>.B <damaged>.B <refine>.B
 ///     <card1>.W <card2>.W <card3>.W <card4>.W <message>.?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)
 void clif_Mail_read(struct map_session_data *sd, int mail_id)
 {
 {
 	int i, fd = sd->fd;
 	int i, fd = sd->fd;
@@ -14932,15 +15201,21 @@ void clif_Mail_read(struct map_session_data *sd, int mail_id)
 		return;
 		return;
 	} else {
 	} else {
 		struct mail_message *msg = &sd->mail.inbox.msg[i];
 		struct mail_message *msg = &sd->mail.inbox.msg[i];
-		struct item *item = &msg->item;
+		struct item *item;
 		struct item_data *data;
 		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 ) {
 		if( msg_len == 0 ) {
-			strcpy(msg->body, "(no message)");
+			strcpy(msg->body, "(no message)"); // TODO: confirm for RODEX
 			msg_len = strlen(msg->body);
 			msg_len = strlen(msg->body);
 		}
 		}
 
 
+#if PACKETVER < 20150513
+		item = &msg->item[0];
+
 		len = 101 + msg_len;
 		len = 101 + msg_len;
 
 
 		WFIFOHEAD(fd,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;
 		WFIFOB(fd,99) = (unsigned char)msg_len;
 		safestrncpy(WFIFOCP(fd,100), msg->body, msg_len + 1);
 		safestrncpy(WFIFOCP(fd,100), msg->body, msg_len + 1);
 		WFIFOSET(fd,len);
 		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) {
 		if (msg->status == MAIL_UNREAD) {
 			msg->status = MAIL_READ;
 			msg->status = MAIL_READ;
 			intif_Mail_read(mail_id);
 			intif_Mail_read(mail_id);
 			clif_parse_Mail_refreshinbox(fd, sd);
 			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]);
 	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 )
 	if( mail_id <= 0 )
 		return;
 		return;
@@ -14993,14 +15319,91 @@ void clif_parse_Mail_read(int fd, struct map_session_data *sd)
 	clif_Mail_read(sd, mail_id);
 	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;
 	int i;
 	bool fail = false;
 	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() )
 	if( !chrif_isconnected() )
 		return;
 		return;
@@ -15013,54 +15416,89 @@ void clif_parse_Mail_getattach(int fd, struct map_session_data *sd)
 	if( i == MAIL_MAX_INBOX )
 	if( i == MAIL_MAX_INBOX )
 		return;
 		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;
+		// No items were found
+		if( i == MAIL_MAX_ITEM ){
+			attachment &= ~MAIL_ATT_ITEM;
+		}
+	}
 
 
-		if ((data = itemdb_exists(sd->mail.inbox.msg[i].item.nameid)) == NULL)
-			return;
+	// Either no attachment requested at all or there are no zeny or items in the mail
+	if( attachment == MAIL_ATT_NONE ){
+		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( 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( fail ) {
-			clif_Mail_getattachment(fd, 1);
-			return;
+	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;
+			}
 		}
 		}
 
 
-		weight = data->weight * sd->mail.inbox.msg[i].item.amount;
-		if( sd->weight + weight > sd->max_weight ) {
-			clif_Mail_getattachment(fd, 2);
+		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;
 			return;
 		}
 		}
+
+		// To make sure another request fails
+		memset(msg->item, 0, MAIL_MAX_ITEM*sizeof(struct item));
 	}
 	}
 
 
-	sd->mail.inbox.msg[i].zeny = 0;
-	memset(&sd->mail.inbox.msg[i].item, 0, sizeof(struct item));
+	intif_mail_getattach(sd,msg,attachment);
 	clif_Mail_read(sd, mail_id);
 	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){
 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 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() )
 	if( !chrif_isconnected() )
 		return;
 		return;
@@ -15073,12 +15511,24 @@ void clif_parse_Mail_delete(int fd, struct map_session_data *sd){
 	if (i < MAIL_MAX_INBOX) {
 	if (i < MAIL_MAX_INBOX) {
 		struct mail_message *msg = &sd->mail.inbox.msg[i];
 		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;
 			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){
 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)];
 	struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)];
 	int idx = RFIFOW(fd,info->pos[0]);
 	int idx = RFIFOW(fd,info->pos[0]);
+#if PACKETVER < 20150513
 	int amount = RFIFOL(fd,info->pos[1]);
 	int amount = RFIFOL(fd,info->pos[1]);
+#else
+	int amount = RFIFOW(fd,info->pos[1]);
+#endif
 	unsigned char flag;
 	unsigned char flag;
 
 
 	if( !chrif_isconnected() )
 	if( !chrif_isconnected() )
@@ -15117,31 +15572,54 @@ void clif_parse_Mail_setattach(int fd, struct map_session_data *sd){
 		return;
 		return;
 
 
 	flag = mail_setitem(sd, idx, amount);
 	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;
+
+	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 (CZ_MAIL_RESET_ITEM).
-/// 0246 <type>.W
+/// Request to reset mail item and/or Zeny
+/// 0246 <type>.W (CZ_MAIL_RESET_ITEM)
 /// type:
 /// type:
 ///     0 = reset all
 ///     0 = reset all
 ///     1 = remove item
 ///     1 = remove item
 ///     2 = remove zeny
 ///     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)
 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]);
 	int type = RFIFOW(fd,packet_db[sd->packet_ver][RFIFOW(fd,0)].pos[0]);
 
 
 	if (type == 0 || type == 1)
 	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)
 	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)];
 	struct s_packet_db* info = &packet_db[sd->packet_ver][RFIFOW(fd,0)];
 
 
 	if( !chrif_isconnected() )
 	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]));
 	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,  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,  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,  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
 	//#0x0A00
 #if PACKETVER >= 20141022
 #if PACKETVER >= 20141022
 	  269,  3,  4,  2,  6, 49,  6,  9, 26, 45, 47, 47, 56, -1,  14,  -1,
 	  269,  3,  4,  2,  6, 49,  6,  9, 26, 45, 47, 47, 56, -1,  14,  -1,
 #else
 #else
 	  269,  0,  0,  2,  6, 48,  6,  9, 26, 45, 47, 47, 56, -1,  14,  0,
 	  269,  0,  0,  2,  6, 48,  6,  9, 26, 45, 47, 47, 56, -1,  14,  0,
 #endif
 #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,
 	   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,
  	  106,  0,  0,  0,  0,  4,  0, 59,  0,  0,  0,  0,  0,  0,  0,  0,
 	//#0x0A40
 	//#0x0A40
 		0,  0,  0, 85, -1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
 		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,
 		0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
 	//#0x0A80
 	//#0x0A80
 		0,  0,  0,  0, 94,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
 		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_setattach,"mailsetattach"},
 		{clif_parse_Mail_winopen,"mailwinopen"},
 		{clif_parse_Mail_winopen,"mailwinopen"},
 		{clif_parse_Mail_send,"mailsend"},
 		{clif_parse_Mail_send,"mailsend"},
+		{clif_parse_Mail_beginwrite,"mailbegin"},
+		{clif_parse_Mail_cancelwrite,"mailcancel"},
+		{clif_parse_Mail_Receiver_Check,"mailreceiver"},
 		// AUCTION SYSTEM
 		// AUCTION SYSTEM
 		{clif_parse_Auction_search,"auctionsearch"},
 		{clif_parse_Auction_search,"auctionsearch"},
 		{clif_parse_Auction_buysell,"auctionbuysell"},
 		{clif_parse_Auction_buysell,"auctionbuysell"},

+ 16 - 5
src/map/clif.h

@@ -866,14 +866,25 @@ void do_init_clif(void);
 void do_final_clif(void);
 void do_final_clif(void);
 
 
 // MAIL SYSTEM
 // 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_window(int fd, int flag);
 void clif_Mail_read(struct map_session_data *sd, int mail_id);
 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_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
 // AUCTION SYSTEM
 void clif_Auction_openwindow(struct map_session_data *sd);
 void clif_Auction_openwindow(struct map_session_data *sd);
 void clif_Auction_results(struct map_session_data *sd, short count, short pages, uint8 *buf);
 void clif_Auction_results(struct map_session_data *sd, short count, short pages, uint8 *buf);

+ 1 - 0
src/map/date.h

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

+ 90 - 33
src/map/intif.c

@@ -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
 	 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
 	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
 	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,-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, 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]
 	-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
  * @param flag 0 Update Inbox | 1 OpenMail
  * @return 0=errur, 1=msg_sent
  * @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())
 	if (CheckForCharServer())
 		return 0;
 		return 0;
 
 
-	WFIFOHEAD(inter_fd,7);
+	WFIFOHEAD(inter_fd,8);
 	WFIFOW(inter_fd,0) = 0x3048;
 	WFIFOW(inter_fd,0) = 0x3048;
 	WFIFOL(inter_fd,2) = char_id;
 	WFIFOL(inter_fd,2) = char_id;
 	WFIFOB(inter_fd,6) = flag;
 	WFIFOB(inter_fd,6) = flag;
-	WFIFOSET(inter_fd,7);
+	WFIFOB(inter_fd,7) = type;
+	WFIFOSET(inter_fd,8);
 
 
 	return 1;
 	return 1;
 }
 }
@@ -2120,24 +2121,29 @@ int intif_parse_Mail_inboxreceived(int fd)
 		return 0;
 		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;
 		return 0;
 	}
 	}
 
 
 	//FIXME: this operation is not safe [ultramage]
 	//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
 	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];
 		char output[128];
 		sprintf(output, msg_txt(sd,510), sd->mail.inbox.unchecked, sd->mail.inbox.unread + sd->mail.inbox.unchecked);
 		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);
 		clif_messagecolor(&sd->bl, color_table[COLOR_LIGHT_GREEN], output, false, SELF);
 	}
 	}
+
 	return 1;
 	return 1;
 }
 }
 
 
@@ -2165,18 +2171,18 @@ int intif_Mail_read(int mail_id)
  * @param mail_id : Mail identification
  * @param mail_id : Mail identification
  * @return 0=error, 1=msg sent
  * @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())
 	if (CheckForCharServer())
-		return 0;
+		return false;
 
 
-	WFIFOHEAD(inter_fd,10);
+	WFIFOHEAD(inter_fd,11);
 	WFIFOW(inter_fd,0) = 0x304a;
 	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)
 int intif_parse_Mail_getattach(int fd)
 {
 {
 	struct map_session_data *sd;
 	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) );
 	sd = map_charid2sd( RFIFOL(fd,4) );
 
 
@@ -2198,15 +2210,17 @@ int intif_parse_Mail_getattach(int fd)
 		return 0;
 		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;
 		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;
 	return 1;
 }
 }
 
 
@@ -2240,6 +2254,7 @@ int intif_parse_Mail_delete(int fd)
 	uint32 char_id = RFIFOL(fd,2);
 	uint32 char_id = RFIFOL(fd,2);
 	int mail_id = RFIFOL(fd,6);
 	int mail_id = RFIFOL(fd,6);
 	bool failed = RFIFOB(fd,10);
 	bool failed = RFIFOB(fd,10);
+	enum mail_inbox_type type;
 
 
 	struct map_session_data *sd = map_charid2sd(char_id);
 	struct map_session_data *sd = map_charid2sd(char_id);
 	if (sd == NULL)
 	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);
 		ARR_FIND(0, MAIL_MAX_INBOX, i, sd->mail.inbox.msg[i].id == mail_id);
 		if( i < MAIL_MAX_INBOX )
 		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));
 			memset(&sd->mail.inbox.msg[i], 0, sizeof(struct mail_message));
 			sd->mail.inbox.amount--;
 			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;
 	return 1;
 }
 }
 
 
@@ -2300,6 +2316,7 @@ int intif_parse_Mail_return(int fd)
 	struct map_session_data *sd = map_charid2sd(RFIFOL(fd,2));
 	struct map_session_data *sd = map_charid2sd(RFIFOL(fd,2));
 	int mail_id = RFIFOL(fd,6);
 	int mail_id = RFIFOL(fd,6);
 	short fail = RFIFOB(fd,10);
 	short fail = RFIFOB(fd,10);
+	enum mail_inbox_type type;
 
 
 	if( sd == NULL )
 	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);
 		ARR_FIND(0, MAIL_MAX_INBOX, i, sd->mail.inbox.msg[i].id == mail_id);
 		if( i < MAIL_MAX_INBOX )
 		if( i < MAIL_MAX_INBOX )
 		{
 		{
+			type = sd->mail.inbox.msg[i].type;
 			memset(&sd->mail.inbox.msg[i], 0, sizeof(struct mail_message));
 			memset(&sd->mail.inbox.msg[i], 0, sizeof(struct mail_message));
 			sd->mail.inbox.amount--;
 			sd->mail.inbox.amount--;
 		}
 		}
 
 
 		if( sd->mail.inbox.full )
 		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);
 	clif_Mail_return(sd->fd, mail_id, fail);
@@ -2379,7 +2397,7 @@ static void intif_parse_Mail_send(int fd)
 			mail_deliveryfail(sd, &msg);
 			mail_deliveryfail(sd, &msg);
 		else
 		else
 		{
 		{
-			clif_Mail_send(sd->fd, false);
+			clif_Mail_send(sd, WRITE_MAIL_SUCCESS);
 			if( save_settings&CHARSAVE_MAIL )
 			if( save_settings&CHARSAVE_MAIL )
 				chrif_save(sd, CSAVE_INVENTORY);
 				chrif_save(sd, CSAVE_INVENTORY);
 		}
 		}
@@ -2399,9 +2417,47 @@ static void intif_parse_Mail_new(int fd)
 
 
 	if( sd == NULL )
 	if( sd == NULL )
 		return;
 		return;
-
 	sd->mail.changed = true;
 	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 0x384b:	intif_parse_Mail_delete(fd); break;
 	case 0x384c:	intif_parse_Mail_return(fd); break;
 	case 0x384c:	intif_parse_Mail_return(fd); break;
 	case 0x384d:	intif_parse_Mail_send(fd); break;
 	case 0x384d:	intif_parse_Mail_send(fd); break;
+	case 0x384e:	intif_parse_Mail_receiver(fd); break;
 
 
 	// Auction System
 	// Auction System
 	case 0x3850:	intif_parse_Auction_results(fd); break;
 	case 0x3850:	intif_parse_Auction_results(fd); break;

+ 3 - 2
src/map/intif.h

@@ -89,12 +89,13 @@ int intif_mercenary_delete(int merc_id);
 int intif_mercenary_save(struct s_mercenary *merc);
 int intif_mercenary_save(struct s_mercenary *merc);
 
 
 // MAIL SYSTEM
 // 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_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_delete(uint32 char_id, int mail_id);
 int intif_Mail_return(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);
 int intif_Mail_send(uint32 account_id, struct mail_message *msg);
+bool intif_mail_checkreceiver(struct map_session_data* sd, char* name);
 // AUCTION SYSTEM
 // AUCTION SYSTEM
 int intif_Auction_requestlist(uint32 char_id, short type, int price, const char* searchtext, short page);
 int intif_Auction_requestlist(uint32 char_id, short type, int price, const char* searchtext, short page);
 int intif_Auction_register(struct auction_data *auction);
 int intif_Auction_register(struct auction_data *auction);

+ 240 - 75
src/map/mail.c

@@ -11,48 +11,98 @@
 #include "clif.h"
 #include "clif.h"
 #include "pc.h"
 #include "pc.h"
 #include "intif.h"
 #include "intif.h"
+#include "date.h" // date_get_dayofyear
 
 
 void mail_clear(struct map_session_data *sd)
 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;
 	sd->mail.zeny = 0;
 
 
 	return;
 	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);
 	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;
 	return 1;
 }
 }
 
 
-int mail_removezeny(struct map_session_data *sd, short flag)
-{
-	nullpo_ret(sd);
-
-	if (flag && sd->mail.zeny > 0)
-	{  //Zeny send
-		pc_payzeny(sd,sd->mail.zeny,LOG_TYPE_MAIL, NULL);
+bool mail_removezeny( struct map_session_data *sd, bool flag ){
+	nullpo_retr( false, sd );
+
+	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;
 	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 sd : player attaching the content
 * @param idx 0 - Zeny; >= 2 - Inventory item
 * @param idx 0 - Zeny; >= 2 - Inventory item
 * @param amount : amout of zeny or number of 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) )
 	if( pc_istrading(sd) )
-		return false;
+		return MAIL_ATTACH_ERROR;
 
 
 	if( idx == 0 ) { // Zeny Transfer
 	if( idx == 0 ) { // Zeny Transfer
 		if( !pc_can_give_items(sd) )
 		if( !pc_can_give_items(sd) )
-			return false;
+			return MAIL_ATTACH_UNTRADEABLE;
 
 
+#if PACKETVER < 20150513
 		if( amount > sd->status.zeny )
 		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;
 		sd->mail.zeny = amount;
 		// clif_updatestatus(sd, SP_ZENY);
 		// clif_updatestatus(sd, SP_ZENY);
-		return true;
+		return MAIL_ATTACH_SUCCESS;
 	} else { // Item Transfer
 	} else { // Item Transfer
+		int i, j, total = 0;
+
 		idx -= 2;
 		idx -= 2;
-		mail_removeitem(sd, 0);
 
 
 		if( idx < 0 || idx >= MAX_INVENTORY )
 		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 )
 		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
 		if( !pc_can_give_items(sd) || sd->inventory.u.items_inventory[idx].expire_time
 			|| !itemdb_available(sd->inventory.u.items_inventory[idx].nameid)
 			|| !itemdb_available(sd->inventory.u.items_inventory[idx].nameid)
 			|| !itemdb_canmail(&sd->inventory.u.items_inventory[idx],pc_get_group_level(sd))
 			|| !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)) )
 			|| (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)
 bool mail_setattachment(struct map_session_data *sd, struct mail_message *msg)
 {
 {
-	int n;
+	int i, amount;
 
 
 	nullpo_retr(false,sd);
 	nullpo_retr(false,sd);
 	nullpo_retr(false,msg);
 	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;
 			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;
 			return false;
 
 
-		if( sd->weight > sd->max_weight )
+		if( sd->weight > sd->max_weight ) // TODO: Why check something weird like this here?
 			return false;
 			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;
 	msg->zeny = sd->mail.zeny;
 
 
 	// Removes the attachment from sender
 	// 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;
 	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( item_received ){
+		clif_mail_getattachment( sd, msg, 0, MAIL_ATT_ITEM );
 	}
 	}
 
 
-	if( zeny > 0 )
-	{  //Zeny receive
+	// Zeny receive
+	if( zeny > 0 ){
 		pc_getzeny(sd, zeny,LOG_TYPE_MAIL, NULL);
 		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;
 	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(sd);
 	nullpo_retv(msg);
 	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
 // This function only check if the mail operations are valid
 bool mail_invalid_operation(struct map_session_data *sd)
 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) )
 	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);
 		ShowWarning("clif_parse_Mail: char '%s' trying to do invalid mail operations.\n", sd->status.name);
 		return true;
 		return true;
 	}
 	}
+#endif
 
 
 	return false;
 	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 ) {
 	if( DIFF_TICK(sd->cansendmail_tick, gettick()) > 0 ) {
 		clif_displaymessage(sd->fd,msg_txt(sd,675)); //"Cannot send mails too fast!!."
 		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;
 		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 )
 	if( body_len > MAIL_BODY_LENGTH )
 		body_len = MAIL_BODY_LENGTH;
 		body_len = MAIL_BODY_LENGTH;
 
 
 	if( !mail_setattachment(sd, &msg) ) { // Invalid Append condition
 	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;
 		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.send_name, sd->status.name, NAME_LENGTH);
 	safestrncpy(msg.dest_name, (char*)dest_name, NAME_LENGTH);
 	safestrncpy(msg.dest_name, (char*)dest_name, NAME_LENGTH);
 	safestrncpy(msg.title, (char*)title, MAIL_TITLE_LENGTH);
 	safestrncpy(msg.title, (char*)title, MAIL_TITLE_LENGTH);
+	msg.type = MAIL_INBOX_NORMAL;
 
 
 	if (msg.title[0] == '\0') {
 	if (msg.title[0] == '\0') {
 		return; // Message has no length and somehow client verification was skipped.
 		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
 	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 );
+	}
+}

+ 20 - 4
src/map/mail.h

@@ -6,15 +6,31 @@
 
 
 #include "../common/mmo.h"
 #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);
 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);
 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);
 int mail_openmail(struct map_session_data *sd);
 void mail_deliveryfail(struct map_session_data *sd, struct mail_message *msg);
 void mail_deliveryfail(struct map_session_data *sd, struct mail_message *msg);
 bool mail_invalid_operation(struct map_session_data *sd);
 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_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_ */
 #endif /* _MAIL_H_ */

+ 1 - 1
src/map/pc.c

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

+ 6 - 2
src/map/pc.h

@@ -262,6 +262,7 @@ struct map_session_data {
 		unsigned int workinprogress : 2; // See clif.h::e_workinprogress
 		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 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 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;
 	} state;
 	struct {
 	struct {
 		unsigned char no_weapon_damage, no_magic_damage, no_misc_damage;
 		unsigned char no_weapon_damage, no_magic_damage, no_misc_damage;
@@ -583,8 +584,11 @@ struct map_session_data {
 
 
 	// Mail System [Zephyrus]
 	// Mail System [Zephyrus]
 	struct s_mail {
 	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;
 		struct mail_data inbox;
 		bool changed; // if true, should sync with charserver on next mailbox request
 		bool changed; // if true, should sync with charserver on next mailbox request
 	} mail;
 	} mail;

+ 20 - 0
src/map/script.c

@@ -23081,6 +23081,25 @@ BUILDIN_FUNC(channel_delete) {
 	return SCRIPT_CMD_SUCCESS;
 	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"
 #include "../custom/script.inc"
 
 
 // declarations that were supposed to be exported from npc_chat.c
 // 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(needed_status_point,"ii?"),
 	BUILDIN_DEF(jobcanentermap,"s?"),
 	BUILDIN_DEF(jobcanentermap,"s?"),
 	BUILDIN_DEF(openstorage2,"ii?"),
 	BUILDIN_DEF(openstorage2,"ii?"),
+	BUILDIN_DEF(unloadnpc, "s"),
 
 
 	// WoE TE
 	// WoE TE
 	BUILDIN_DEF(agitstart3,""),
 	BUILDIN_DEF(agitstart3,""),

+ 1 - 0
src/map/script_constants.h

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

+ 3 - 0
src/map/skill.c

@@ -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_SPRITEMABLE:		case SC_BITESCAR:
 					case SC_CLAN_INFO:		case SC_SWORDCLAN:		case SC_ARCWANDCLAN:
 					case SC_CLAN_INFO:		case SC_SWORDCLAN:		case SC_ARCWANDCLAN:
 					case SC_GOLDENMACECLAN:	case SC_CROSSBOWCLAN:
 					case SC_GOLDENMACECLAN:	case SC_CROSSBOWCLAN:
+					case SC_DAILYSENDMAILCNT:
 						continue;
 						continue;
 					case SC_WHISTLE:		case SC_ASSNCROS:		case SC_POEMBRAGI:
 					case SC_WHISTLE:		case SC_ASSNCROS:		case SC_POEMBRAGI:
 					case SC_APPLEIDUN:		case SC_HUMMING:		case SC_DONTFORGETME:
 					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_GOLDENMACECLAN:
 					case SC_CROSSBOWCLAN:
 					case SC_CROSSBOWCLAN:
 					case SC_JUMPINGCLAN:
 					case SC_JUMPINGCLAN:
+					case SC_DAILYSENDMAILCNT:
 						continue;
 						continue;
 					case SC_WHISTLE:
 					case SC_WHISTLE:
 					case SC_ASSNCROS:
 					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_QUEST_BUFF1:	case SC_QUEST_BUFF2:	case SC_QUEST_BUFF3:
 					case SC_CLAN_INFO:		case SC_SWORDCLAN:		case SC_ARCWANDCLAN:
 					case SC_CLAN_INFO:		case SC_SWORDCLAN:		case SC_ARCWANDCLAN:
 					case SC_GOLDENMACECLAN:	case SC_CROSSBOWCLAN:
 					case SC_GOLDENMACECLAN:	case SC_CROSSBOWCLAN:
+					case SC_DAILYSENDMAILCNT:
 					continue;
 					continue;
 				case SC_ASSUMPTIO:
 				case SC_ASSUMPTIO:
 					if( bl->type == BL_MOB )
 					if( bl->type == BL_MOB )

+ 10 - 0
src/map/status.c

@@ -1116,6 +1116,9 @@ void initChangeTables(void)
     StatusIconChangeTable[SC_GEFFEN_MAGIC2] = SI_GEFFEN_MAGIC2;
     StatusIconChangeTable[SC_GEFFEN_MAGIC2] = SI_GEFFEN_MAGIC2;
     StatusIconChangeTable[SC_GEFFEN_MAGIC3] = SI_GEFFEN_MAGIC3;
     StatusIconChangeTable[SC_GEFFEN_MAGIC3] = SI_GEFFEN_MAGIC3;
 
 
+	// RODEX
+	StatusIconChangeTable[SC_DAILYSENDMAILCNT] = SI_DAILYSENDMAILCNT;
+
 	/* Other SC which are not necessarily associated to skills */
 	/* Other SC which are not necessarily associated to skills */
 	StatusChangeFlagTable[SC_ASPDPOTION0] |= SCB_ASPD;
 	StatusChangeFlagTable[SC_ASPDPOTION0] |= SCB_ASPD;
 	StatusChangeFlagTable[SC_ASPDPOTION1] |= 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_CROSSBOWCLAN] |= SCB_DEX|SCB_AGI|SCB_MAXHP|SCB_MAXSP;
 	StatusChangeFlagTable[SC_JUMPINGCLAN] |= SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK;
 	StatusChangeFlagTable[SC_JUMPINGCLAN] |= SCB_STR|SCB_AGI|SCB_VIT|SCB_INT|SCB_DEX|SCB_LUK;
 
 
+	// RODEX
+	StatusChangeFlagTable[SC_DAILYSENDMAILCNT] |= SCB_NONE;
+
 #ifdef RENEWAL
 #ifdef RENEWAL
 	// renewal EDP increases your weapon atk
 	// renewal EDP increases your weapon atk
 	StatusChangeFlagTable[SC_EDP] |= SCB_WATK;
 	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_PUSH_CART:
 		case SC_SPRITEMABLE:
 		case SC_SPRITEMABLE:
 		case SC_CLAN_INFO:
 		case SC_CLAN_INFO:
+		case SC_DAILYSENDMAILCNT:
 			tick = -1;
 			tick = -1;
 			break;
 			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_UPHEAVAL_OPTION:
 		case SC_CIRCLE_OF_FIRE_OPTION:
 		case SC_CIRCLE_OF_FIRE_OPTION:
 		case SC_CLAN_INFO:
 		case SC_CLAN_INFO:
+		case SC_DAILYSENDMAILCNT:
 			val_flag |= 1|2;
 			val_flag |= 1|2;
 			break;
 			break;
 		// Start |1|2|4 val_flag setting
 		// 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_GOLDENMACECLAN:
 			case SC_CROSSBOWCLAN:
 			case SC_CROSSBOWCLAN:
 			case SC_JUMPINGCLAN:
 			case SC_JUMPINGCLAN:
+			case SC_DAILYSENDMAILCNT:
 				continue;
 				continue;
 			}
 			}
 		}
 		}
@@ -11545,6 +11554,7 @@ int status_change_clear(struct block_list* bl, int type)
 			case SC_GOLDENMACECLAN:
 			case SC_GOLDENMACECLAN:
 			case SC_CROSSBOWCLAN:
 			case SC_CROSSBOWCLAN:
 			case SC_JUMPINGCLAN:
 			case SC_JUMPINGCLAN:
+			case SC_DAILYSENDMAILCNT:
 				continue;
 				continue;
 			}
 			}
 		}
 		}

+ 2 - 0
src/map/status.h

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