1 | /* SCCS Id: @(#)mail.c 3.3 1999/08/24 */ 2 | /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ 3 | /* NetHack may be freely redistributed. See license for details. */ 4 | 5 | #include "hack.h" 6 | 7 | #ifdef MAIL 8 | #include "mail.h" 9 | 10 | /* 11 | * Notify user when new mail has arrived. Idea by Merlyn Leroy. 12 | * 13 | * The mail daemon can move with less than usual restraint. It can: 14 | * - move diagonally from a door 15 | * - use secret and closed doors 16 | * - run through a monster ("Gangway!", etc.) 17 | * - run over pools & traps 18 | * 19 | * Possible extensions: 20 | * - Open the file MAIL and do fstat instead of stat for efficiency. 21 | * (But sh uses stat, so this cannot be too bad.) 22 | * - Examine the mail and produce a scroll of mail named "From somebody". 23 | * - Invoke MAILREADER in such a way that only this single letter is read. 24 | * - Do something to the text when the scroll is enchanted or cancelled. 25 | * - Make the daemon always appear at a stairwell, and have it find a 26 | * path to the hero. 27 | * 28 | * Note by Olaf Seibert: On the Amiga, we usually don't get mail. So we go 29 | * through most of the effects at 'random' moments. 30 | * Note by Paul Winner: The MSDOS port also 'fakes' the mail daemon at 31 | * random intervals. 32 | */ 33 | 34 | STATIC_DCL boolean FDECL(md_start,(coord *)); 35 | STATIC_DCL boolean FDECL(md_stop,(coord *, coord *)); 36 | STATIC_DCL boolean FDECL(md_rush,(struct monst *,int,int)); 37 | STATIC_DCL void FDECL(newmail, (struct mail_info *)); 38 | 39 | extern char *viz_rmin, *viz_rmax; /* line-of-sight limits (vision.c) */ 40 | 41 | #ifdef OVL0 42 | 43 | # if !defined(UNIX) && !defined(VMS) && !defined(LAN_MAIL) 44 | int mustgetmail = -1; 45 | # endif 46 | 47 | #endif /* OVL0 */ 48 | #ifdef OVLB 49 | 50 | # ifdef UNIX 51 | #include <sys/stat.h> 52 | #include <pwd.h> 53 | /* DON'T trust all Unices to declare getpwuid() in <pwd.h> */ 54 | # if !defined(_BULL_SOURCE) && !defined(__sgi) && !defined(_M_UNIX) 55 | # if !defined(SUNOS4) && !(defined(ULTRIX) && defined(__GNUC__)) 56 | /* DO trust all SVR4 to typedef uid_t in <sys/types.h> (probably to a long) */ 57 | # if defined(POSIX_TYPES) || defined(SVR4) || defined(HPUX) 58 | extern struct passwd *FDECL(getpwuid,(uid_t)); 59 | # else 60 | extern struct passwd *FDECL(getpwuid,(int)); 61 | # endif 62 | # endif 63 | # endif 64 | static struct stat omstat,nmstat; 65 | static char *mailbox = (char *)0; 66 | static long laststattime; 67 | 68 | # if !defined(MAILPATH) && defined(AMS) /* Just a placeholder for AMS */ 69 | # define MAILPATH "/dev/null" 70 | # endif 71 | # if !defined(MAILPATH) && (defined(LINUX) || defined(__osf__)) 72 | # define MAILPATH "/var/spool/mail/" 73 | # endif 74 | # if !defined(MAILPATH) && defined(__FreeBSD__) 75 | # define MAILPATH "/var/mail/" 76 | # endif 77 | # if !defined(MAILPATH) && (defined(BSD) || defined(ULTRIX)) 78 | # define MAILPATH "/usr/spool/mail/" 79 | # endif 80 | # if !defined(MAILPATH) && (defined(SYSV) || defined(HPUX)) 81 | # define MAILPATH "/usr/mail/" 82 | # endif 83 | 84 | void 85 | getmailstatus() 86 | { 87 | if(!mailbox && !(mailbox = nh_getenv("MAIL"))) { 88 | # ifdef MAILPATH 89 | # ifdef AMS 90 | struct passwd ppasswd; 91 | 92 | (void) memcpy(&ppasswd, getpwuid(getuid()), sizeof(struct passwd)); 93 | if (ppasswd.pw_dir) { 94 | mailbox = (char *) alloc((unsigned) strlen(ppasswd.pw_dir)+sizeof(AMS_MAILBOX)); 95 | Strcpy(mailbox, ppasswd.pw_dir); 96 | Strcat(mailbox, AMS_MAILBOX); 97 | } else 98 | return; 99 | # else 100 | const char *pw_name = getpwuid(getuid())->pw_name; 101 | mailbox = (char *) alloc(sizeof(MAILPATH)+strlen(pw_name)); 102 | Strcpy(mailbox, MAILPATH); 103 | Strcat(mailbox, pw_name); 104 | # endif /* AMS */ 105 | # else 106 | return; 107 | # endif 108 | } 109 | if(stat(mailbox, &omstat)){ 110 | # ifdef PERMANENT_MAILBOX 111 | pline("Cannot get status of MAIL=\"%s\".", mailbox); 112 | mailbox = 0; 113 | # else 114 | omstat.st_mtime = 0; 115 | # endif 116 | } 117 | } 118 | # endif /* UNIX */ 119 | 120 | #endif /* OVLB */ 121 | #ifdef OVL0 122 | 123 | /* 124 | * Pick coordinates for a starting position for the mail daemon. Called 125 | * from newmail() and newphone(). 126 | */ 127 | STATIC_OVL boolean 128 | md_start(startp) 129 | coord *startp; 130 | { 131 | coord testcc; /* scratch coordinates */ 132 | int row; /* current row we are checking */ 133 | int lax; /* if TRUE, pick a position in sight. */ 134 | int dd; /* distance to current point */ 135 | int max_distance; /* max distance found so far */ 136 | 137 | /* 138 | * If blind and not telepathic, then it doesn't matter what we pick --- 139 | * the hero is not going to see it anyway. So pick a nearby position. 140 | */ 141 | if (Blind && !Blind_telepat) { 142 | if (!enexto(startp, u.ux, u.uy, (struct permonst *) 0)) 143 | return FALSE; /* no good posiitons */ 144 | return TRUE; 145 | } 146 | 147 | /* 148 | * Arrive at an up or down stairwell if it is in line of sight from the 149 | * hero. 150 | */ 151 | if (couldsee(upstair.sx, upstair.sy)) { 152 | startp->x = upstair.sx; 153 | startp->y = upstair.sy; 154 | return TRUE; 155 | } 156 | if (couldsee(dnstair.sx, dnstair.sy)) { 157 | startp->x = dnstair.sx; 158 | startp->y = dnstair.sy; 159 | return TRUE; 160 | } 161 | 162 | /* 163 | * Try to pick a location out of sight next to the farthest position away 164 | * from the hero. If this fails, try again, just picking the farthest 165 | * position that could be seen. What we really ought to be doing is 166 | * finding a path from a stairwell... 167 | * 168 | * The arrays viz_rmin[] and viz_rmax[] are set even when blind. These 169 | * are the LOS limits for each row. 170 | */ 171 | lax = 0; /* be picky */ 172 | max_distance = -1; 173 | retry: 174 | for (row = 0; row < ROWNO; row++) { 175 | if (viz_rmin[row] < viz_rmax[row]) { 176 | /* There are valid positions on this row. */ 177 | dd = distu(viz_rmin[row],row); 178 | if (dd > max_distance) { 179 | if (lax) { 180 | max_distance = dd; 181 | startp->y = row; 182 | startp->x = viz_rmin[row]; 183 | 184 | } else if (enexto(&testcc, (xchar)viz_rmin[row], row, 185 | (struct permonst *) 0) && 186 | !cansee(testcc.x, testcc.y) && 187 | couldsee(testcc.x, testcc.y)) { 188 | max_distance = dd; 189 | *startp = testcc; 190 | } 191 | } 192 | dd = distu(viz_rmax[row],row); 193 | if (dd > max_distance) { 194 | if (lax) { 195 | max_distance = dd; 196 | startp->y = row; 197 | startp->x = viz_rmax[row]; 198 | 199 | } else if (enexto(&testcc, (xchar)viz_rmax[row], row, 200 | (struct permonst *) 0) && 201 | !cansee(testcc.x,testcc.y) && 202 | couldsee(testcc.x, testcc.y)) { 203 | 204 | max_distance = dd; 205 | *startp = testcc; 206 | } 207 | } 208 | } 209 | } 210 | 211 | if (max_distance < 0) { 212 | if (!lax) { 213 | lax = 1; /* just find a position */ 214 | goto retry; 215 | } 216 | return FALSE; 217 | } 218 | 219 | return TRUE; 220 | } 221 | 222 | /* 223 | * Try to choose a stopping point as near as possible to the starting 224 | * position while still adjacent to the hero. If all else fails, try 225 | * enexto(). Use enexto() as a last resort because enexto() chooses 226 | * its point randomly, which is not what we want. 227 | */ 228 | STATIC_OVL boolean 229 | md_stop(stopp, startp) 230 | coord *stopp; /* stopping position (we fill it in) */ 231 | coord *startp; /* starting positon (read only) */ 232 | { 233 | int x, y, distance, min_distance = -1; 234 | 235 | for (x = u.ux-1; x <= u.ux+1; x++) 236 | for (y = u.uy-1; y <= u.uy+1; y++) { 237 | if (!isok(x, y) || (x == u.ux && y == u.uy)) continue; 238 | 239 | if (ACCESSIBLE(levl[x][y].typ) && !MON_AT(x,y)) { 240 | distance = dist2(x,y,startp->x,startp->y); 241 | if (min_distance < 0 || distance < min_distance || 242 | (distance == min_distance && rn2(2))) { 243 | stopp->x = x; 244 | stopp->y = y; 245 | min_distance = distance; 246 | } 247 | } 248 | } 249 | 250 | /* If we didn't find a good spot, try enexto(). */ 251 | if (min_distance < 0 && 252 | !enexto(stopp, u.ux, u.uy, &mons[PM_MAIL_DAEMON])) 253 | return FALSE; 254 | 255 | return TRUE; 256 | } 257 | 258 | /* Let the mail daemon have a larger vocabulary. */ 259 | static NEARDATA const char *mail_text[] = { 260 | "Gangway!", 261 | "Look out!", 262 | "Pardon me!" 263 | }; 264 | #define md_exclamations() (mail_text[rn2(3)]) 265 | 266 | /* 267 | * Make the mail daemon run through the dungeon. The daemon will run over 268 | * any monsters that are in its path, but will replace them later. Return 269 | * FALSE if the md gets stuck in a position where there is a monster. Return 270 | * TRUE otherwise. 271 | */ 272 | STATIC_OVL boolean 273 | md_rush(md,tx,ty) 274 | struct monst *md; 275 | register int tx, ty; /* destination of mail daemon */ 276 | { 277 | struct monst *mon; /* displaced monster */ 278 | register int dx, dy; /* direction counters */ 279 | int fx = md->mx, fy = md->my; /* current location */ 280 | int nfx = fx, nfy = fy, /* new location */ 281 | d1, d2; /* shortest distances */ 282 | 283 | /* 284 | * It is possible that the monster at (fx,fy) is not the md when: 285 | * the md rushed the hero and failed, and is now starting back. 286 | */ 287 | if (m_at(fx, fy) == md) { 288 | remove_monster(fx, fy); /* pick up from orig position */ 289 | newsym(fx, fy); 290 | } 291 | 292 | /* 293 | * At the beginning and exit of this loop, md is not placed in the 294 | * dungeon. 295 | */ 296 | while (1) { 297 | /* Find a good location next to (fx,fy) closest to (tx,ty). */ 298 | d1 = dist2(fx,fy,tx,ty); 299 | for (dx = -1; dx <= 1; dx++) for(dy = -1; dy <= 1; dy++) 300 | if ((dx || dy) && isok(fx+dx,fy+dy) && 301 | !IS_STWALL(levl[fx+dx][fy+dy].typ)) { 302 | d2 = dist2(fx+dx,fy+dy,tx,ty); 303 | if (d2 < d1) { 304 | d1 = d2; 305 | nfx = fx+dx; 306 | nfy = fy+dy; 307 | } 308 | } 309 | 310 | /* Break if the md couldn't find a new position. */ 311 | if (nfx == fx && nfy == fy) break; 312 | 313 | fx = nfx; /* this is our new position */ 314 | fy = nfy; 315 | 316 | /* Break if the md reaches its destination. */ 317 | if (fx == tx && fy == ty) break; 318 | 319 | if ((mon = m_at(fx,fy)) != 0) /* save monster at this position */ 320 | verbalize(md_exclamations()); 321 | else if (fx == u.ux && fy == u.uy) 322 | verbalize("Excuse me."); 323 | 324 | place_monster(md,fx,fy); /* put md down */ 325 | newsym(fx,fy); /* see it */ 326 | flush_screen(0); /* make sure md shows up */ 327 | delay_output(); /* wait a little bit */ 328 | 329 | /* Remove md from the dungeon. Restore original mon, if necessary. */ 330 | if (mon) { 331 | if ((mon->mx != fx) || (mon->my != fy)) 332 | place_worm_seg(mon, fx, fy); 333 | else 334 | place_monster(mon, fx, fy); 335 | } else 336 | remove_monster(fx, fy); 337 | newsym(fx,fy); 338 | } 339 | 340 | /* 341 | * Check for a monster at our stopping position (this is possible, but 342 | * very unlikely). If one exists, then have the md leave in disgust. 343 | */ 344 | if ((mon = m_at(fx, fy)) != 0) { 345 | place_monster(md, fx, fy); /* display md with text below */ 346 | newsym(fx, fy); 347 | verbalize("This place's too crowded. I'm outta here."); 348 | 349 | if ((mon->mx != fx) || (mon->my != fy)) /* put mon back */ 350 | place_worm_seg(mon, fx, fy); 351 | else 352 | place_monster(mon, fx, fy); 353 | 354 | newsym(fx, fy); 355 | return FALSE; 356 | } 357 | 358 | place_monster(md, fx, fy); /* place at final spot */ 359 | newsym(fx, fy); 360 | flush_screen(0); 361 | delay_output(); /* wait a little bit */ 362 | 363 | return TRUE; 364 | } 365 | 366 | /* Deliver a scroll of mail. */ 367 | /*ARGSUSED*/ 368 | STATIC_OVL void 369 | newmail(info) 370 | struct mail_info *info; 371 | { 372 | struct monst *md; 373 | coord start, stop; 374 | boolean message_seen = FALSE; 375 | 376 | /* Try to find good starting and stopping places. */ 377 | if (!md_start(&start) || !md_stop(&stop,&start)) goto give_up; 378 | 379 | /* Make the daemon. Have it rush towards the hero. */ 380 | if (!(md = makemon(&mons[PM_MAIL_DAEMON], start.x, start.y, NO_MM_FLAGS))) 381 | goto give_up; 382 | if (!md_rush(md, stop.x, stop.y)) goto go_back; 383 | 384 | message_seen = TRUE; 385 | verbalize("%s, %s! %s.", Hello(md), plname, info->display_txt); 386 | 387 | if (info->message_typ) { 388 | struct obj *obj = mksobj(SCR_MAIL, FALSE, FALSE); 389 | if (distu(md->mx,md->my) > 2) 390 | verbalize("Catch!"); 391 | display_nhwindow(WIN_MESSAGE, FALSE); 392 | if (info->object_nam) { 393 | obj = oname(obj, info->object_nam); 394 | if (info->response_cmd) { /*(hide extension of the obj name)*/ 395 | int namelth = info->response_cmd - info->object_nam - 1; 396 | if ( namelth <= 0 || namelth >= (int) obj->onamelth ) 397 | impossible("mail delivery screwed up"); 398 | else 399 | *(ONAME(obj) + namelth) = '\0'; 400 | /* Note: renaming object will discard the hidden command. */ 401 | } 402 | } 403 | obj = hold_another_object(obj, "Oops!", 404 | (const char *)0, (const char *)0); 405 | } 406 | 407 | /* zip back to starting location */ 408 | go_back: 409 | (void) md_rush(md, start.x, start.y); 410 | mongone(md); 411 | /* deliver some classes of messages even if no daemon ever shows up */ 412 | give_up: 413 | if (!message_seen && info->message_typ == MSG_OTHER) 414 | pline("Hark! \"%s.\"", info->display_txt); 415 | } 416 | 417 | # if !defined(UNIX) && !defined(VMS) && !defined(LAN_MAIL) 418 | 419 | void 420 | ckmailstatus() 421 | { 422 | if (u.uswallow || !flags.biff) return; 423 | if (mustgetmail < 0) { 424 | #if defined(AMIGA) || defined(MSDOS) || defined(TOS) 425 | mustgetmail=(moves<2000)?(100+rn2(2000)):(2000+rn2(3000)); 426 | #endif 427 | return; 428 | } 429 | if (--mustgetmail <= 0) { 430 | static struct mail_info 431 | deliver = {MSG_MAIL,"I have some mail for you",0,0}; 432 | newmail(&deliver); 433 | mustgetmail = -1; 434 | } 435 | } 436 | 437 | /*ARGSUSED*/ 438 | void 439 | readmail(otmp) 440 | struct obj *otmp; 441 | { 442 | static char *junk[] = { 443 | "Please disregard previous letter.", 444 | "Welcome to NetHack.", 445 | #ifdef AMIGA 446 | "Only Amiga makes it possible.", 447 | "CATS have all the answers.", 448 | #endif 449 | "Report bugs to <devteam@nethack.org>." 450 | }; 451 | 452 | if (Blind) { 453 | pline("Unfortunately you cannot see what it says."); 454 | } else 455 | pline("It reads: \"%s\"", junk[rn2(SIZE(junk))]); 456 | 457 | } 458 | 459 | # endif /* !UNIX && !VMS && !LAN_MAIL */ 460 | 461 | # ifdef UNIX 462 | 463 | void 464 | ckmailstatus() 465 | { 466 | if(!mailbox || u.uswallow || !flags.biff 467 | # ifdef MAILCKFREQ 468 | || moves < laststattime + MAILCKFREQ 469 | # endif 470 | ) 471 | return; 472 | 473 | laststattime = moves; 474 | if(stat(mailbox, &nmstat)){ 475 | # ifdef PERMANENT_MAILBOX 476 | pline("Cannot get status of MAIL=\"%s\" anymore.", mailbox); 477 | mailbox = 0; 478 | # else 479 | nmstat.st_mtime = 0; 480 | # endif 481 | } else if(nmstat.st_mtime > omstat.st_mtime) { 482 | if (nmstat.st_size) { 483 | static struct mail_info deliver = { 484 | # ifndef NO_MAILREADER 485 | MSG_MAIL, "I have some mail for you", 486 | # else 487 | /* suppress creation and delivery of scroll of mail */ 488 | MSG_OTHER, "You have some mail in the outside world", 489 | # endif 490 | 0, 0 491 | }; 492 | newmail(&deliver); 493 | } 494 | getmailstatus(); /* might be too late ... */ 495 | } 496 | } 497 | 498 | /*ARGSUSED*/ 499 | void 500 | readmail(otmp) 501 | struct obj *otmp; 502 | { 503 | # ifdef DEF_MAILREADER /* This implies that UNIX is defined */ 504 | register const char *mr = 0; 505 | 506 | display_nhwindow(WIN_MESSAGE, FALSE); 507 | if(!(mr = nh_getenv("MAILREADER"))) 508 | mr = DEF_MAILREADER; 509 | 510 | if(child(1)){ 511 | (void) execl(mr, mr, (char *)0); 512 | terminate(EXIT_FAILURE); 513 | } 514 | # else 515 | # ifndef AMS /* AMS mailboxes are directories */ 516 | display_file(mailbox, TRUE); 517 | # endif /* AMS */ 518 | # endif /* DEF_MAILREADER */ 519 | 520 | /* get new stat; not entirely correct: there is a small time 521 | window where we do not see new mail */ 522 | getmailstatus(); 523 | } 524 | 525 | # endif /* UNIX */ 526 | 527 | # ifdef VMS 528 | 529 | extern NDECL(struct mail_info *parse_next_broadcast); 530 | 531 | volatile int broadcasts = 0; 532 | 533 | void 534 | ckmailstatus() 535 | { 536 | struct mail_info *brdcst; 537 | 538 | if (u.uswallow || !flags.biff) return; 539 | 540 | while (broadcasts > 0) { /* process all trapped broadcasts [until] */ 541 | broadcasts--; 542 | if ((brdcst = parse_next_broadcast()) != 0) { 543 | newmail(brdcst); 544 | break; /* only handle one real message at a time */ 545 | } 546 | } 547 | } 548 | 549 | void 550 | readmail(otmp) 551 | struct obj *otmp; 552 | { 553 | # ifdef SHELL /* can't access mail reader without spawning subprocess */ 554 | const char *txt, *cmd; 555 | char *p, buf[BUFSZ], qbuf[BUFSZ]; 556 | int len; 557 | 558 | /* there should be a command hidden beyond the object name */ 559 | txt = otmp->onamelth ? ONAME(otmp) : ""; 560 | len = strlen(txt); 561 | cmd = (len + 1 < otmp->onamelth) ? txt + len + 1 : (char *) 0; 562 | if (!cmd || !*cmd) cmd = "SPAWN"; 563 | 564 | Sprintf(qbuf, "System command (%s)", cmd); 565 | getlin(qbuf, buf); 566 | if (*buf != '\033') { 567 | for (p = eos(buf); p > buf; *p = '\0') 568 | if (*--p != ' ') break; /* strip trailing spaces */ 569 | if (*buf) cmd = buf; /* use user entered command */ 570 | if (!strcmpi(cmd, "SPAWN") || !strcmp(cmd, "!")) 571 | cmd = (char *) 0; /* interactive escape */ 572 | 573 | vms_doshell(cmd, TRUE); 574 | (void) sleep(1); 575 | } 576 | # endif /* SHELL */ 577 | } 578 | 579 | # endif /* VMS */ 580 | 581 | # ifdef LAN_MAIL 582 | 583 | void 584 | ckmailstatus() 585 | { 586 | static int laststattime = 0; 587 | 588 | if(u.uswallow || !flags.biff 589 | # ifdef MAILCKFREQ 590 | || moves < laststattime + MAILCKFREQ 591 | # endif 592 | ) 593 | return; 594 | 595 | laststattime = moves; 596 | if (lan_mail_check()) { 597 | static struct mail_info deliver = { 598 | # ifndef NO_MAILREADER 599 | MSG_MAIL, "I have some mail for you", 600 | # else 601 | /* suppress creation and delivery of scroll of mail */ 602 | MSG_OTHER, "You have some mail in the outside world", 603 | # endif 604 | 0, 0 605 | }; 606 | newmail(&deliver); 607 | } 608 | } 609 | 610 | /*ARGSUSED*/ 611 | void 612 | readmail(otmp) 613 | struct obj *otmp; 614 | { 615 | lan_mail_read(otmp); 616 | } 617 | 618 | # endif /* LAN_MAIL */ 619 | 620 | #endif /* OVL0 */ 621 | 622 | #endif /* MAIL */ 623 | 624 | /*mail.c*/