mail.cpp 14 KB

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