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