mail.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. // Copyright (c) rAthena Dev Teams - Licensed under GNU GPL
  2. // For more information, see LICENCE in the main folder
  3. #include "mail.hpp"
  4. #include "../common/nullpo.hpp"
  5. #include "../common/showmsg.hpp"
  6. #include "../common/strlib.hpp"
  7. #include "../common/timer.hpp"
  8. #include "atcommand.hpp"
  9. #include "battle.hpp"
  10. #include "clif.hpp"
  11. #include "date.hpp" // date_get_dayofyear
  12. #include "intif.hpp"
  13. #include "itemdb.hpp"
  14. #include "log.hpp"
  15. #include "pc.hpp"
  16. #include "pet.hpp"
  17. void mail_clear(struct map_session_data *sd)
  18. {
  19. int i;
  20. for( i = 0; i < MAIL_MAX_ITEM; i++ ){
  21. sd->mail.item[i].nameid = 0;
  22. sd->mail.item[i].index = 0;
  23. sd->mail.item[i].amount = 0;
  24. }
  25. sd->mail.zeny = 0;
  26. return;
  27. }
  28. int mail_removeitem(struct map_session_data *sd, short flag, int idx, int amount)
  29. {
  30. int i;
  31. nullpo_ret(sd);
  32. idx -= 2;
  33. if( idx < 0 || idx >= MAX_INVENTORY )
  34. return false;
  35. if( amount <= 0 || amount > sd->inventory.u.items_inventory[idx].amount )
  36. return false;
  37. ARR_FIND(0, MAIL_MAX_ITEM, i, sd->mail.item[i].index == idx && sd->mail.item[i].nameid > 0);
  38. if( i == MAIL_MAX_ITEM ){
  39. return false;
  40. }
  41. if( flag ){
  42. if( battle_config.mail_attachment_price > 0 ){
  43. if( pc_payzeny( sd, battle_config.mail_attachment_price, LOG_TYPE_MAIL, NULL ) ){
  44. return false;
  45. }
  46. }
  47. #if PACKETVER < 20150513
  48. // With client update packet
  49. pc_delitem(sd, idx, amount, 1, 0, LOG_TYPE_MAIL);
  50. #else
  51. // RODEX refreshes the client inventory from the ACK packet
  52. pc_delitem(sd, idx, amount, 0, 0, LOG_TYPE_MAIL);
  53. #endif
  54. }else{
  55. sd->mail.item[i].amount -= amount;
  56. // Item was removed completely
  57. if( sd->mail.item[i].amount <= 0 ){
  58. // Move the rest of the array forward
  59. for( ; i < MAIL_MAX_ITEM - 1; i++ ){
  60. if ( sd->mail.item[i + 1].nameid == 0 ){
  61. break;
  62. }
  63. sd->mail.item[i].index = sd->mail.item[i+1].index;
  64. sd->mail.item[i].nameid = sd->mail.item[i+1].nameid;
  65. sd->mail.item[i].amount = sd->mail.item[i+1].amount;
  66. }
  67. // Zero the rest
  68. for( ; i < MAIL_MAX_ITEM; i++ ){
  69. sd->mail.item[i].index = 0;
  70. sd->mail.item[i].nameid = 0;
  71. sd->mail.item[i].amount = 0;
  72. }
  73. }
  74. #if PACKETVER < 20150513
  75. clif_additem(sd, idx, amount, 0);
  76. #else
  77. clif_mail_removeitem(sd, true, idx + 2, amount);
  78. #endif
  79. }
  80. return 1;
  81. }
  82. bool mail_removezeny( struct map_session_data *sd, bool flag ){
  83. nullpo_retr( false, sd );
  84. if( sd->mail.zeny > 0 ){
  85. //Zeny send
  86. if( flag ){
  87. if( pc_payzeny( sd, sd->mail.zeny + sd->mail.zeny * battle_config.mail_zeny_fee / 100, LOG_TYPE_MAIL, NULL ) ){
  88. return false;
  89. }
  90. }else{
  91. // Update is called by pc_payzeny, so only call it in the else condition
  92. clif_updatestatus(sd, SP_ZENY);
  93. }
  94. }
  95. sd->mail.zeny = 0;
  96. return true;
  97. }
  98. /**
  99. * Attempt to set item or zeny to a mail
  100. * @param sd : player attaching the content
  101. * @param idx 0 - Zeny; >= 2 - Inventory item
  102. * @param amount : amout of zeny or number of item
  103. * @return see enum mail_attach_result in mail.hpp
  104. */
  105. enum mail_attach_result mail_setitem(struct map_session_data *sd, short idx, uint32 amount) {
  106. if( pc_istrading(sd) )
  107. return MAIL_ATTACH_ERROR;
  108. if( idx == 0 ) { // Zeny Transfer
  109. if( !pc_can_give_items(sd) )
  110. return MAIL_ATTACH_UNTRADEABLE;
  111. #if PACKETVER < 20150513
  112. if( amount > sd->status.zeny )
  113. amount = sd->status.zeny; // TODO: confirm this behavior for old mail system
  114. #else
  115. if( ( amount + battle_config.mail_zeny_fee / 100 * amount ) > sd->status.zeny )
  116. return MAIL_ATTACH_ERROR;
  117. #endif
  118. sd->mail.zeny = amount;
  119. // clif_updatestatus(sd, SP_ZENY);
  120. return MAIL_ATTACH_SUCCESS;
  121. } else { // Item Transfer
  122. int i;
  123. #if PACKETVER >= 20150513
  124. int j, total = 0;
  125. #endif
  126. idx -= 2;
  127. if( idx < 0 || idx >= MAX_INVENTORY || sd->inventory_data[idx] == nullptr )
  128. return MAIL_ATTACH_ERROR;
  129. if (itemdb_ishatched_egg(&sd->inventory.u.items_inventory[idx]))
  130. return MAIL_ATTACH_ERROR;
  131. if( sd->inventory.u.items_inventory[idx].equipSwitch ){
  132. return MAIL_ATTACH_EQUIPSWITCH;
  133. }
  134. #if PACKETVER < 20150513
  135. i = 0;
  136. // Remove existing item
  137. mail_removeitem(sd, 0, sd->mail.item[i].index + 2, sd->mail.item[i].amount);
  138. #else
  139. ARR_FIND(0, MAIL_MAX_ITEM, i, sd->mail.item[i].index == idx && sd->mail.item[i].nameid > 0 );
  140. // The same item had already been added to the mail
  141. if( i < MAIL_MAX_ITEM ){
  142. // Check if it is stackable
  143. if( !itemdb_isstackable(sd->mail.item[i].nameid) ){
  144. return MAIL_ATTACH_ERROR;
  145. }
  146. // Check if it exceeds the total amount
  147. if( ( amount + sd->mail.item[i].amount ) > sd->inventory.u.items_inventory[idx].amount ){
  148. return MAIL_ATTACH_ERROR;
  149. }
  150. // Check if it exceeds the total weight
  151. if( battle_config.mail_attachment_weight ){
  152. for( j = 0; j < i; j++ ){
  153. total += sd->mail.item[j].amount * ( sd->inventory_data[sd->mail.item[j].index]->weight / 10 );
  154. }
  155. total += amount * sd->inventory_data[idx]->weight / 10;
  156. if( total > battle_config.mail_attachment_weight ){
  157. return MAIL_ATTACH_WEIGHT;
  158. }
  159. }
  160. sd->mail.item[i].amount += amount;
  161. return MAIL_ATTACH_SUCCESS;
  162. }else{
  163. ARR_FIND(0, MAIL_MAX_ITEM, i, sd->mail.item[i].nameid == 0);
  164. if( i == MAIL_MAX_ITEM ){
  165. return MAIL_ATTACH_SPACE;
  166. }
  167. // Check if it exceeds the total weight
  168. if( battle_config.mail_attachment_weight ){
  169. for( j = 0; j < i; j++ ){
  170. total += sd->mail.item[j].amount * ( sd->inventory_data[sd->mail.item[j].index]->weight / 10 );
  171. }
  172. total += amount * sd->inventory_data[idx]->weight / 10;
  173. if( total > battle_config.mail_attachment_weight ){
  174. return MAIL_ATTACH_WEIGHT;
  175. }
  176. }
  177. }
  178. #endif
  179. if( amount > sd->inventory.u.items_inventory[idx].amount )
  180. return MAIL_ATTACH_ERROR;
  181. if( !pc_can_give_items(sd) || sd->inventory.u.items_inventory[idx].expire_time
  182. || !itemdb_available(sd->inventory.u.items_inventory[idx].nameid)
  183. || !itemdb_canmail(&sd->inventory.u.items_inventory[idx],pc_get_group_level(sd))
  184. || (sd->inventory.u.items_inventory[idx].bound && !pc_can_give_bounded_items(sd)) )
  185. return MAIL_ATTACH_UNTRADEABLE;
  186. sd->mail.item[i].index = idx;
  187. sd->mail.item[i].nameid = sd->inventory.u.items_inventory[idx].nameid;
  188. sd->mail.item[i].amount = amount;
  189. return MAIL_ATTACH_SUCCESS;
  190. }
  191. }
  192. bool mail_setattachment(struct map_session_data *sd, struct mail_message *msg)
  193. {
  194. int i, amount;
  195. nullpo_retr(false,sd);
  196. nullpo_retr(false,msg);
  197. for( i = 0, amount = 0; i < MAIL_MAX_ITEM; i++ ){
  198. int index = sd->mail.item[i].index;
  199. if( sd->mail.item[i].nameid == 0 || sd->mail.item[i].amount == 0 ){
  200. memset(&msg->item[i], 0x00, sizeof(struct item));
  201. continue;
  202. }
  203. amount++;
  204. if( sd->inventory.u.items_inventory[index].nameid != sd->mail.item[i].nameid )
  205. return false;
  206. if( sd->inventory.u.items_inventory[index].amount < sd->mail.item[i].amount )
  207. return false;
  208. if( sd->weight > sd->max_weight ) // TODO: Why check something weird like this here?
  209. return false;
  210. memcpy(&msg->item[i], &sd->inventory.u.items_inventory[index], sizeof(struct item));
  211. msg->item[i].amount = sd->mail.item[i].amount;
  212. }
  213. 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 )
  214. return false;
  215. msg->zeny = sd->mail.zeny;
  216. // Removes the attachment from sender
  217. for( i = 0; i < MAIL_MAX_ITEM; i++ ){
  218. if( sd->mail.item[i].nameid == 0 || sd->mail.item[i].amount == 0 ){
  219. // Exit the loop on the first empty entry
  220. break;
  221. }
  222. mail_removeitem(sd,1,sd->mail.item[i].index + 2,sd->mail.item[i].amount);
  223. }
  224. mail_removezeny(sd,true);
  225. return true;
  226. }
  227. void mail_getattachment(struct map_session_data* sd, struct mail_message* msg, int zeny, struct item* item){
  228. int i;
  229. bool item_received = false;
  230. for( i = 0; i < MAIL_MAX_ITEM; i++ ){
  231. if( item->nameid > 0 && item->amount > 0 ){
  232. // If no card or special card id is set
  233. if( item[i].card[0] == 0 ){
  234. // Check if it is a pet egg
  235. std::shared_ptr<s_pet_db> pet = pet_db_search( item[i].nameid, PET_EGG );
  236. // If it is a pet egg and the card data does not contain a pet id (see if clause above)
  237. if( pet != nullptr ){
  238. // Create a new pet
  239. pet_create_egg( sd, item[i].nameid );
  240. item_received = true;
  241. continue;
  242. }
  243. }
  244. // Add the item normally
  245. pc_additem( sd, &item[i], item[i].amount, LOG_TYPE_MAIL );
  246. item_received = true;
  247. }
  248. }
  249. if( item_received ){
  250. clif_mail_getattachment( sd, msg, 0, MAIL_ATT_ITEM );
  251. }
  252. // Zeny receive
  253. if( zeny > 0 ){
  254. pc_getzeny(sd, zeny,LOG_TYPE_MAIL, NULL);
  255. clif_mail_getattachment( sd, msg, 0, MAIL_ATT_ZENY );
  256. }
  257. }
  258. int mail_openmail(struct map_session_data *sd)
  259. {
  260. nullpo_ret(sd);
  261. if( sd->state.storage_flag || sd->state.vending || sd->state.buyingstore || sd->state.trading )
  262. return 0;
  263. clif_Mail_window(sd->fd, 0);
  264. return 1;
  265. }
  266. void mail_deliveryfail(struct map_session_data *sd, struct mail_message *msg){
  267. int i, zeny = 0;
  268. nullpo_retv(sd);
  269. nullpo_retv(msg);
  270. for( i = 0; i < MAIL_MAX_ITEM; i++ ){
  271. if( msg->item[i].amount > 0 ){
  272. // Item receive (due to failure)
  273. pc_additem(sd, &msg->item[i], msg->item[i].amount, LOG_TYPE_MAIL);
  274. zeny += battle_config.mail_attachment_price;
  275. }
  276. }
  277. if( msg->zeny > 0 ){
  278. pc_getzeny(sd,msg->zeny + msg->zeny*battle_config.mail_zeny_fee/100 + zeny,LOG_TYPE_MAIL, NULL); //Zeny receive (due to failure)
  279. }
  280. clif_Mail_send(sd, WRITE_MAIL_FAILED);
  281. }
  282. // This function only check if the mail operations are valid
  283. bool mail_invalid_operation(struct map_session_data *sd)
  284. {
  285. #if PACKETVER < 20150513
  286. if( !map_getmapflag(sd->bl.m, MF_TOWN) && !pc_can_use_command(sd, "mail", COMMAND_ATCOMMAND) )
  287. {
  288. ShowWarning("clif_parse_Mail: char '%s' trying to do invalid mail operations.\n", sd->status.name);
  289. return true;
  290. }
  291. #endif
  292. return false;
  293. }
  294. /**
  295. * Attempt to send mail
  296. * @param sd Sender
  297. * @param dest_name Destination name
  298. * @param title Mail title
  299. * @param body_msg Mail message
  300. * @param body_len Message's length
  301. */
  302. void mail_send(struct map_session_data *sd, const char *dest_name, const char *title, const char *body_msg, int body_len) {
  303. struct mail_message msg;
  304. nullpo_retv(sd);
  305. if( sd->state.trading )
  306. return;
  307. if( DIFF_TICK(sd->cansendmail_tick, gettick()) > 0 ) {
  308. clif_displaymessage(sd->fd,msg_txt(sd,675)); //"Cannot send mails too fast!!."
  309. clif_Mail_send(sd, WRITE_MAIL_FAILED); // fail
  310. return;
  311. }
  312. if( battle_config.mail_daily_count ){
  313. mail_refresh_remaining_amount(sd);
  314. // After calling mail_refresh_remaining_amount the status should always be there
  315. if( sd->sc.data[SC_DAILYSENDMAILCNT] == NULL || sd->sc.data[SC_DAILYSENDMAILCNT]->val2 >= battle_config.mail_daily_count ){
  316. clif_Mail_send(sd, WRITE_MAIL_FAILED_CNT);
  317. return;
  318. }else{
  319. sc_start2( &sd->bl, &sd->bl, SC_DAILYSENDMAILCNT, 100, date_get_dayofyear(), sd->sc.data[SC_DAILYSENDMAILCNT]->val2 + 1, INFINITE_TICK );
  320. }
  321. }
  322. if( body_len > MAIL_BODY_LENGTH )
  323. body_len = MAIL_BODY_LENGTH;
  324. if( !mail_setattachment(sd, &msg) ) { // Invalid Append condition
  325. int i;
  326. clif_Mail_send(sd, WRITE_MAIL_FAILED); // fail
  327. for( i = 0; i < MAIL_MAX_ITEM; i++ ){
  328. mail_removeitem(sd,0,sd->mail.item[i].index + 2, sd->mail.item[i].amount);
  329. }
  330. mail_removezeny(sd,false);
  331. return;
  332. }
  333. msg.id = 0; // id will be assigned by charserver
  334. msg.send_id = sd->status.char_id;
  335. msg.dest_id = 0; // will attempt to resolve name
  336. safestrncpy(msg.send_name, sd->status.name, NAME_LENGTH);
  337. safestrncpy(msg.dest_name, (char*)dest_name, NAME_LENGTH);
  338. safestrncpy(msg.title, (char*)title, MAIL_TITLE_LENGTH);
  339. msg.type = MAIL_INBOX_NORMAL;
  340. if (msg.title[0] == '\0') {
  341. return; // Message has no length and somehow client verification was skipped.
  342. }
  343. if (body_len)
  344. safestrncpy(msg.body, (char*)body_msg, min(body_len + 1, MAIL_BODY_LENGTH));
  345. else
  346. memset(msg.body, 0x00, MAIL_BODY_LENGTH);
  347. msg.timestamp = time(NULL);
  348. if( !intif_Mail_send(sd->status.account_id, &msg) )
  349. mail_deliveryfail(sd, &msg);
  350. sd->cansendmail_tick = gettick() + battle_config.mail_delay; // Flood Protection
  351. }
  352. void mail_refresh_remaining_amount( struct map_session_data* sd ){
  353. int doy = date_get_dayofyear();
  354. nullpo_retv(sd);
  355. // If it was not yet started or it was started on another day
  356. if( sd->sc.data[SC_DAILYSENDMAILCNT] == NULL || sd->sc.data[SC_DAILYSENDMAILCNT]->val1 != doy ){
  357. sc_start2( &sd->bl, &sd->bl, SC_DAILYSENDMAILCNT, 100, doy, 0, INFINITE_TICK );
  358. }
  359. }