/* SCCS Id: @(#)mail.c 3.3 1999/08/24 */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" #ifdef MAIL #include "mail.h" /* * Notify user when new mail has arrived. Idea by Merlyn Leroy. * * The mail daemon can move with less than usual restraint. It can: * - move diagonally from a door * - use secret and closed doors * - run through a monster ("Gangway!", etc.) * - run over pools & traps * * Possible extensions: * - Open the file MAIL and do fstat instead of stat for efficiency. * (But sh uses stat, so this cannot be too bad.) * - Examine the mail and produce a scroll of mail named "From somebody". * - Invoke MAILREADER in such a way that only this single letter is read. * - Do something to the text when the scroll is enchanted or cancelled. * - Make the daemon always appear at a stairwell, and have it find a * path to the hero. * * Note by Olaf Seibert: On the Amiga, we usually don't get mail. So we go * through most of the effects at 'random' moments. * Note by Paul Winner: The MSDOS port also 'fakes' the mail daemon at * random intervals. */ STATIC_DCL boolean FDECL(md_start,(coord *)); STATIC_DCL boolean FDECL(md_stop,(coord *, coord *)); STATIC_DCL boolean FDECL(md_rush,(struct monst *,int,int)); STATIC_DCL void FDECL(newmail, (struct mail_info *)); extern char *viz_rmin, *viz_rmax; /* line-of-sight limits (vision.c) */ #ifdef OVL0 # if !defined(UNIX) && !defined(VMS) && !defined(LAN_MAIL) int mustgetmail = -1; # endif #endif /* OVL0 */ #ifdef OVLB # ifdef UNIX #include #include /* DON'T trust all Unices to declare getpwuid() in */ # if !defined(_BULL_SOURCE) && !defined(__sgi) && !defined(_M_UNIX) # if !defined(SUNOS4) && !(defined(ULTRIX) && defined(__GNUC__)) /* DO trust all SVR4 to typedef uid_t in (probably to a long) */ # if defined(POSIX_TYPES) || defined(SVR4) || defined(HPUX) extern struct passwd *FDECL(getpwuid,(uid_t)); # else extern struct passwd *FDECL(getpwuid,(int)); # endif # endif # endif static struct stat omstat,nmstat; static char *mailbox = (char *)0; static long laststattime; # if !defined(MAILPATH) && defined(AMS) /* Just a placeholder for AMS */ # define MAILPATH "/dev/null" # endif # if !defined(MAILPATH) && (defined(LINUX) || defined(__osf__)) # define MAILPATH "/var/spool/mail/" # endif # if !defined(MAILPATH) && defined(__FreeBSD__) # define MAILPATH "/var/mail/" # endif # if !defined(MAILPATH) && (defined(BSD) || defined(ULTRIX)) # define MAILPATH "/usr/spool/mail/" # endif # if !defined(MAILPATH) && (defined(SYSV) || defined(HPUX)) # define MAILPATH "/usr/mail/" # endif void getmailstatus() { if(!mailbox && !(mailbox = nh_getenv("MAIL"))) { # ifdef MAILPATH # ifdef AMS struct passwd ppasswd; (void) memcpy(&ppasswd, getpwuid(getuid()), sizeof(struct passwd)); if (ppasswd.pw_dir) { mailbox = (char *) alloc((unsigned) strlen(ppasswd.pw_dir)+sizeof(AMS_MAILBOX)); Strcpy(mailbox, ppasswd.pw_dir); Strcat(mailbox, AMS_MAILBOX); } else return; # else const char *pw_name = getpwuid(getuid())->pw_name; mailbox = (char *) alloc(sizeof(MAILPATH)+strlen(pw_name)); Strcpy(mailbox, MAILPATH); Strcat(mailbox, pw_name); # endif /* AMS */ # else return; # endif } if(stat(mailbox, &omstat)){ # ifdef PERMANENT_MAILBOX pline("Cannot get status of MAIL=\"%s\".", mailbox); mailbox = 0; # else omstat.st_mtime = 0; # endif } } # endif /* UNIX */ #endif /* OVLB */ #ifdef OVL0 /* * Pick coordinates for a starting position for the mail daemon. Called * from newmail() and newphone(). */ STATIC_OVL boolean md_start(startp) coord *startp; { coord testcc; /* scratch coordinates */ int row; /* current row we are checking */ int lax; /* if TRUE, pick a position in sight. */ int dd; /* distance to current point */ int max_distance; /* max distance found so far */ /* * If blind and not telepathic, then it doesn't matter what we pick --- * the hero is not going to see it anyway. So pick a nearby position. */ if (Blind && !Blind_telepat) { if (!enexto(startp, u.ux, u.uy, (struct permonst *) 0)) return FALSE; /* no good posiitons */ return TRUE; } /* * Arrive at an up or down stairwell if it is in line of sight from the * hero. */ if (couldsee(upstair.sx, upstair.sy)) { startp->x = upstair.sx; startp->y = upstair.sy; return TRUE; } if (couldsee(dnstair.sx, dnstair.sy)) { startp->x = dnstair.sx; startp->y = dnstair.sy; return TRUE; } /* * Try to pick a location out of sight next to the farthest position away * from the hero. If this fails, try again, just picking the farthest * position that could be seen. What we really ought to be doing is * finding a path from a stairwell... * * The arrays viz_rmin[] and viz_rmax[] are set even when blind. These * are the LOS limits for each row. */ lax = 0; /* be picky */ max_distance = -1; retry: for (row = 0; row < ROWNO; row++) { if (viz_rmin[row] < viz_rmax[row]) { /* There are valid positions on this row. */ dd = distu(viz_rmin[row],row); if (dd > max_distance) { if (lax) { max_distance = dd; startp->y = row; startp->x = viz_rmin[row]; } else if (enexto(&testcc, (xchar)viz_rmin[row], row, (struct permonst *) 0) && !cansee(testcc.x, testcc.y) && couldsee(testcc.x, testcc.y)) { max_distance = dd; *startp = testcc; } } dd = distu(viz_rmax[row],row); if (dd > max_distance) { if (lax) { max_distance = dd; startp->y = row; startp->x = viz_rmax[row]; } else if (enexto(&testcc, (xchar)viz_rmax[row], row, (struct permonst *) 0) && !cansee(testcc.x,testcc.y) && couldsee(testcc.x, testcc.y)) { max_distance = dd; *startp = testcc; } } } } if (max_distance < 0) { if (!lax) { lax = 1; /* just find a position */ goto retry; } return FALSE; } return TRUE; } /* * Try to choose a stopping point as near as possible to the starting * position while still adjacent to the hero. If all else fails, try * enexto(). Use enexto() as a last resort because enexto() chooses * its point randomly, which is not what we want. */ STATIC_OVL boolean md_stop(stopp, startp) coord *stopp; /* stopping position (we fill it in) */ coord *startp; /* starting positon (read only) */ { int x, y, distance, min_distance = -1; for (x = u.ux-1; x <= u.ux+1; x++) for (y = u.uy-1; y <= u.uy+1; y++) { if (!isok(x, y) || (x == u.ux && y == u.uy)) continue; if (ACCESSIBLE(levl[x][y].typ) && !MON_AT(x,y)) { distance = dist2(x,y,startp->x,startp->y); if (min_distance < 0 || distance < min_distance || (distance == min_distance && rn2(2))) { stopp->x = x; stopp->y = y; min_distance = distance; } } } /* If we didn't find a good spot, try enexto(). */ if (min_distance < 0 && !enexto(stopp, u.ux, u.uy, &mons[PM_MAIL_DAEMON])) return FALSE; return TRUE; } /* Let the mail daemon have a larger vocabulary. */ static NEARDATA const char *mail_text[] = { "Gangway!", "Look out!", "Pardon me!" }; #define md_exclamations() (mail_text[rn2(3)]) /* * Make the mail daemon run through the dungeon. The daemon will run over * any monsters that are in its path, but will replace them later. Return * FALSE if the md gets stuck in a position where there is a monster. Return * TRUE otherwise. */ STATIC_OVL boolean md_rush(md,tx,ty) struct monst *md; register int tx, ty; /* destination of mail daemon */ { struct monst *mon; /* displaced monster */ register int dx, dy; /* direction counters */ int fx = md->mx, fy = md->my; /* current location */ int nfx = fx, nfy = fy, /* new location */ d1, d2; /* shortest distances */ /* * It is possible that the monster at (fx,fy) is not the md when: * the md rushed the hero and failed, and is now starting back. */ if (m_at(fx, fy) == md) { remove_monster(fx, fy); /* pick up from orig position */ newsym(fx, fy); } /* * At the beginning and exit of this loop, md is not placed in the * dungeon. */ while (1) { /* Find a good location next to (fx,fy) closest to (tx,ty). */ d1 = dist2(fx,fy,tx,ty); for (dx = -1; dx <= 1; dx++) for(dy = -1; dy <= 1; dy++) if ((dx || dy) && isok(fx+dx,fy+dy) && !IS_STWALL(levl[fx+dx][fy+dy].typ)) { d2 = dist2(fx+dx,fy+dy,tx,ty); if (d2 < d1) { d1 = d2; nfx = fx+dx; nfy = fy+dy; } } /* Break if the md couldn't find a new position. */ if (nfx == fx && nfy == fy) break; fx = nfx; /* this is our new position */ fy = nfy; /* Break if the md reaches its destination. */ if (fx == tx && fy == ty) break; if ((mon = m_at(fx,fy)) != 0) /* save monster at this position */ verbalize(md_exclamations()); else if (fx == u.ux && fy == u.uy) verbalize("Excuse me."); place_monster(md,fx,fy); /* put md down */ newsym(fx,fy); /* see it */ flush_screen(0); /* make sure md shows up */ delay_output(); /* wait a little bit */ /* Remove md from the dungeon. Restore original mon, if necessary. */ if (mon) { if ((mon->mx != fx) || (mon->my != fy)) place_worm_seg(mon, fx, fy); else place_monster(mon, fx, fy); } else remove_monster(fx, fy); newsym(fx,fy); } /* * Check for a monster at our stopping position (this is possible, but * very unlikely). If one exists, then have the md leave in disgust. */ if ((mon = m_at(fx, fy)) != 0) { place_monster(md, fx, fy); /* display md with text below */ newsym(fx, fy); verbalize("This place's too crowded. I'm outta here."); if ((mon->mx != fx) || (mon->my != fy)) /* put mon back */ place_worm_seg(mon, fx, fy); else place_monster(mon, fx, fy); newsym(fx, fy); return FALSE; } place_monster(md, fx, fy); /* place at final spot */ newsym(fx, fy); flush_screen(0); delay_output(); /* wait a little bit */ return TRUE; } /* Deliver a scroll of mail. */ /*ARGSUSED*/ STATIC_OVL void newmail(info) struct mail_info *info; { struct monst *md; coord start, stop; boolean message_seen = FALSE; /* Try to find good starting and stopping places. */ if (!md_start(&start) || !md_stop(&stop,&start)) goto give_up; /* Make the daemon. Have it rush towards the hero. */ if (!(md = makemon(&mons[PM_MAIL_DAEMON], start.x, start.y, NO_MM_FLAGS))) goto give_up; if (!md_rush(md, stop.x, stop.y)) goto go_back; message_seen = TRUE; verbalize("%s, %s! %s.", Hello(md), plname, info->display_txt); if (info->message_typ) { struct obj *obj = mksobj(SCR_MAIL, FALSE, FALSE); if (distu(md->mx,md->my) > 2) verbalize("Catch!"); display_nhwindow(WIN_MESSAGE, FALSE); if (info->object_nam) { obj = oname(obj, info->object_nam); if (info->response_cmd) { /*(hide extension of the obj name)*/ int namelth = info->response_cmd - info->object_nam - 1; if ( namelth <= 0 || namelth >= (int) obj->onamelth ) impossible("mail delivery screwed up"); else *(ONAME(obj) + namelth) = '\0'; /* Note: renaming object will discard the hidden command. */ } } obj = hold_another_object(obj, "Oops!", (const char *)0, (const char *)0); } /* zip back to starting location */ go_back: (void) md_rush(md, start.x, start.y); mongone(md); /* deliver some classes of messages even if no daemon ever shows up */ give_up: if (!message_seen && info->message_typ == MSG_OTHER) pline("Hark! \"%s.\"", info->display_txt); } # if !defined(UNIX) && !defined(VMS) && !defined(LAN_MAIL) void ckmailstatus() { if (u.uswallow || !flags.biff) return; if (mustgetmail < 0) { #if defined(AMIGA) || defined(MSDOS) || defined(TOS) mustgetmail=(moves<2000)?(100+rn2(2000)):(2000+rn2(3000)); #endif return; } if (--mustgetmail <= 0) { static struct mail_info deliver = {MSG_MAIL,"I have some mail for you",0,0}; newmail(&deliver); mustgetmail = -1; } } /*ARGSUSED*/ void readmail(otmp) struct obj *otmp; { static char *junk[] = { "Please disregard previous letter.", "Welcome to NetHack.", #ifdef AMIGA "Only Amiga makes it possible.", "CATS have all the answers.", #endif "Report bugs to ." }; if (Blind) { pline("Unfortunately you cannot see what it says."); } else pline("It reads: \"%s\"", junk[rn2(SIZE(junk))]); } # endif /* !UNIX && !VMS && !LAN_MAIL */ # ifdef UNIX void ckmailstatus() { if(!mailbox || u.uswallow || !flags.biff # ifdef MAILCKFREQ || moves < laststattime + MAILCKFREQ # endif ) return; laststattime = moves; if(stat(mailbox, &nmstat)){ # ifdef PERMANENT_MAILBOX pline("Cannot get status of MAIL=\"%s\" anymore.", mailbox); mailbox = 0; # else nmstat.st_mtime = 0; # endif } else if(nmstat.st_mtime > omstat.st_mtime) { if (nmstat.st_size) { static struct mail_info deliver = { # ifndef NO_MAILREADER MSG_MAIL, "I have some mail for you", # else /* suppress creation and delivery of scroll of mail */ MSG_OTHER, "You have some mail in the outside world", # endif 0, 0 }; newmail(&deliver); } getmailstatus(); /* might be too late ... */ } } /*ARGSUSED*/ void readmail(otmp) struct obj *otmp; { # ifdef DEF_MAILREADER /* This implies that UNIX is defined */ register const char *mr = 0; display_nhwindow(WIN_MESSAGE, FALSE); if(!(mr = nh_getenv("MAILREADER"))) mr = DEF_MAILREADER; if(child(1)){ (void) execl(mr, mr, (char *)0); terminate(EXIT_FAILURE); } # else # ifndef AMS /* AMS mailboxes are directories */ display_file(mailbox, TRUE); # endif /* AMS */ # endif /* DEF_MAILREADER */ /* get new stat; not entirely correct: there is a small time window where we do not see new mail */ getmailstatus(); } # endif /* UNIX */ # ifdef VMS extern NDECL(struct mail_info *parse_next_broadcast); volatile int broadcasts = 0; void ckmailstatus() { struct mail_info *brdcst; if (u.uswallow || !flags.biff) return; while (broadcasts > 0) { /* process all trapped broadcasts [until] */ broadcasts--; if ((brdcst = parse_next_broadcast()) != 0) { newmail(brdcst); break; /* only handle one real message at a time */ } } } void readmail(otmp) struct obj *otmp; { # ifdef SHELL /* can't access mail reader without spawning subprocess */ const char *txt, *cmd; char *p, buf[BUFSZ], qbuf[BUFSZ]; int len; /* there should be a command hidden beyond the object name */ txt = otmp->onamelth ? ONAME(otmp) : ""; len = strlen(txt); cmd = (len + 1 < otmp->onamelth) ? txt + len + 1 : (char *) 0; if (!cmd || !*cmd) cmd = "SPAWN"; Sprintf(qbuf, "System command (%s)", cmd); getlin(qbuf, buf); if (*buf != '\033') { for (p = eos(buf); p > buf; *p = '\0') if (*--p != ' ') break; /* strip trailing spaces */ if (*buf) cmd = buf; /* use user entered command */ if (!strcmpi(cmd, "SPAWN") || !strcmp(cmd, "!")) cmd = (char *) 0; /* interactive escape */ vms_doshell(cmd, TRUE); (void) sleep(1); } # endif /* SHELL */ } # endif /* VMS */ # ifdef LAN_MAIL void ckmailstatus() { static int laststattime = 0; if(u.uswallow || !flags.biff # ifdef MAILCKFREQ || moves < laststattime + MAILCKFREQ # endif ) return; laststattime = moves; if (lan_mail_check()) { static struct mail_info deliver = { # ifndef NO_MAILREADER MSG_MAIL, "I have some mail for you", # else /* suppress creation and delivery of scroll of mail */ MSG_OTHER, "You have some mail in the outside world", # endif 0, 0 }; newmail(&deliver); } } /*ARGSUSED*/ void readmail(otmp) struct obj *otmp; { lan_mail_read(otmp); } # endif /* LAN_MAIL */ #endif /* OVL0 */ #endif /* MAIL */ /*mail.c*/