1 /* NetHack 3.6 mail.c $NHDT-Date: 1519070343 2018/02/19 19:59:03 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.31 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /*-Copyright (c) Pasi Kallinen, 2018. */
4 /* NetHack may be freely redistributed. See license for details. */
6 /* JNetHack Copyright */
7 /* (c) Issei Numata, Naoki Hamada, Shigehiro Miyashita, 1994-2000 */
8 /* For 3.4-, Copyright (c) SHIRAKATA Kentaro, 2002-2016 */
9 /* JNetHack may be freely redistributed. See license for details. */
17 #endif /* SIMPLE_MAIL */
21 * Notify user when new mail has arrived. Idea by Merlyn Leroy.
23 * The mail daemon can move with less than usual restraint. It can:
24 * - move diagonally from a door
25 * - use secret and closed doors
26 * - run through a monster ("Gangway!", etc.)
27 * - run over pools & traps
29 * Possible extensions:
30 * - Open the file MAIL and do fstat instead of stat for efficiency.
31 * (But sh uses stat, so this cannot be too bad.)
32 * - Examine the mail and produce a scroll of mail named "From somebody".
33 * - Invoke MAILREADER in such a way that only this single mail is read.
34 * - Do something to the text when the scroll is enchanted or cancelled.
35 * - Make the daemon always appear at a stairwell, and have it find a
38 * Note by Olaf Seibert: On the Amiga, we usually don't get mail. So we go
39 * through most of the effects at 'random' moments.
40 * Note by Paul Winner: The MSDOS port also 'fakes' the mail daemon at
44 STATIC_DCL boolean FDECL(md_start, (coord *));
45 STATIC_DCL boolean FDECL(md_stop, (coord *, coord *));
46 STATIC_DCL boolean FDECL(md_rush, (struct monst *, int, int));
47 STATIC_DCL void FDECL(newmail, (struct mail_info *));
49 extern char *viz_rmin, *viz_rmax; /* line-of-sight limits (vision.c) */
51 #if !defined(UNIX) && !defined(VMS)
58 /* DON'T trust all Unices to declare getpwuid() in <pwd.h> */
59 #if !defined(_BULL_SOURCE) && !defined(__sgi) && !defined(_M_UNIX)
60 #if !defined(SUNOS4) && !(defined(ULTRIX) && defined(__GNUC__))
61 /* DO trust all SVR4 to typedef uid_t in <sys/types.h> (probably to a long) */
62 #if defined(POSIX_TYPES) || defined(SVR4) || defined(HPUX)
63 extern struct passwd *FDECL(getpwuid, (uid_t));
65 extern struct passwd *FDECL(getpwuid, (int));
69 static struct stat omstat, nmstat;
70 static char *mailbox = (char *) 0;
71 static long laststattime;
73 #if !defined(MAILPATH) && defined(AMS) /* Just a placeholder for AMS */
74 #define MAILPATH "/dev/null"
76 #if !defined(MAILPATH) && (defined(LINUX) || defined(__osf__))
77 #define MAILPATH "/var/spool/mail/"
79 #if !defined(MAILPATH) && defined(__FreeBSD__)
80 #define MAILPATH "/var/mail/"
82 #if !defined(MAILPATH) && (defined(BSD) || defined(ULTRIX))
83 #define MAILPATH "/usr/spool/mail/"
85 #if !defined(MAILPATH) && (defined(SYSV) || defined(HPUX))
86 #define MAILPATH "/usr/mail/"
93 free((genericptr_t) mailbox), mailbox = (char *) 0;
100 ; /* no need to repeat the setup */
101 } else if ((mailbox = nh_getenv("MAIL")) != 0) {
102 mailbox = dupstr(mailbox);
106 struct passwd ppasswd;
108 (void) memcpy(&ppasswd, getpwuid(getuid()), sizeof (struct passwd));
109 if (ppasswd.pw_dir) {
110 /* note: 'sizeof "LITERAL"' includes +1 for terminating '\0' */
111 mailbox = (char *) alloc((unsigned) (strlen(ppasswd.pw_dir)
112 + sizeof AMS_MAILBOX));
113 Strcpy(mailbox, ppasswd.pw_dir);
114 Strcat(mailbox, AMS_MAILBOX);
117 const char *pw_name = getpwuid(getuid())->pw_name;
119 /* note: 'sizeof "LITERAL"' includes +1 for terminating '\0' */
120 mailbox = (char *) alloc((unsigned) (strlen(pw_name)
122 Strcpy(mailbox, MAILPATH);
123 Strcat(mailbox, pw_name);
125 #endif /* MAILPATH */
128 debugpline3("mailbox=%c%s%c",
129 mailbox ? '\"' : '<',
130 mailbox ? mailbox : "null",
131 mailbox ? '\"' : '>');
133 if (mailbox && stat(mailbox, &omstat)) {
134 #ifdef PERMANENT_MAILBOX
135 pline("Cannot get status of MAIL=\"%s\".", mailbox);
136 free_maildata(); /* set 'mailbox' to Null */
145 * Pick coordinates for a starting position for the mail daemon. Called
146 * from newmail() and newphone().
152 coord testcc; /* scratch coordinates */
153 int row; /* current row we are checking */
154 int lax; /* if TRUE, pick a position in sight. */
155 int dd; /* distance to current point */
156 int max_distance; /* max distance found so far */
159 * If blind and not telepathic, then it doesn't matter what we pick ---
160 * the hero is not going to see it anyway. So pick a nearby position.
162 if (Blind && !Blind_telepat) {
163 if (!enexto(startp, u.ux, u.uy, (struct permonst *) 0))
164 return FALSE; /* no good positions */
169 * Arrive at an up or down stairwell if it is in line of sight from the
172 if (couldsee(upstair.sx, upstair.sy)) {
173 startp->x = upstair.sx;
174 startp->y = upstair.sy;
177 if (couldsee(dnstair.sx, dnstair.sy)) {
178 startp->x = dnstair.sx;
179 startp->y = dnstair.sy;
184 * Try to pick a location out of sight next to the farthest position away
185 * from the hero. If this fails, try again, just picking the farthest
186 * position that could be seen. What we really ought to be doing is
187 * finding a path from a stairwell...
189 * The arrays viz_rmin[] and viz_rmax[] are set even when blind. These
190 * are the LOS limits for each row.
192 lax = 0; /* be picky */
195 for (row = 0; row < ROWNO; row++) {
196 if (viz_rmin[row] < viz_rmax[row]) {
197 /* There are valid positions on this row. */
198 dd = distu(viz_rmin[row], row);
199 if (dd > max_distance) {
203 startp->x = viz_rmin[row];
205 } else if (enexto(&testcc, (xchar) viz_rmin[row], row,
206 (struct permonst *) 0)
207 && !cansee(testcc.x, testcc.y)
208 && couldsee(testcc.x, testcc.y)) {
213 dd = distu(viz_rmax[row], row);
214 if (dd > max_distance) {
218 startp->x = viz_rmax[row];
220 } else if (enexto(&testcc, (xchar) viz_rmax[row], row,
221 (struct permonst *) 0)
222 && !cansee(testcc.x, testcc.y)
223 && couldsee(testcc.x, testcc.y)) {
231 if (max_distance < 0) {
233 lax = 1; /* just find a position */
243 * Try to choose a stopping point as near as possible to the starting
244 * position while still adjacent to the hero. If all else fails, try
245 * enexto(). Use enexto() as a last resort because enexto() chooses
246 * its point randomly, which is not what we want.
249 md_stop(stopp, startp)
250 coord *stopp; /* stopping position (we fill it in) */
251 coord *startp; /* starting position (read only) */
253 int x, y, distance, min_distance = -1;
255 for (x = u.ux - 1; x <= u.ux + 1; x++)
256 for (y = u.uy - 1; y <= u.uy + 1; y++) {
257 if (!isok(x, y) || (x == u.ux && y == u.uy))
260 if (accessible(x, y) && !MON_AT(x, y)) {
261 distance = dist2(x, y, startp->x, startp->y);
262 if (min_distance < 0 || distance < min_distance
263 || (distance == min_distance && rn2(2))) {
266 min_distance = distance;
271 /* If we didn't find a good spot, try enexto(). */
272 if (min_distance < 0 && !enexto(stopp, u.ux, u.uy, &mons[PM_MAIL_DAEMON]))
278 /* Let the mail daemon have a larger vocabulary. */
280 static NEARDATA const char *mail_text[] = { "Gangway!", "Look out!",
283 static NEARDATA const char *mail_text[] = { "
\82Ç
\82¢
\82½
\82Ç
\82¢
\82½
\81I", "
\8bC
\82ð
\82Â
\82¯
\82ë
\81I",
284 "
\82¶
\82á
\82Ü
\82·
\82é
\82æ
\81I" };
286 #define md_exclamations() (mail_text[rn2(3)])
289 * Make the mail daemon run through the dungeon. The daemon will run over
290 * any monsters that are in its path, but will replace them later. Return
291 * FALSE if the md gets stuck in a position where there is a monster. Return
297 register int tx, ty; /* destination of mail daemon */
299 struct monst *mon; /* displaced monster */
300 register int dx, dy; /* direction counters */
301 int fx = md->mx, fy = md->my; /* current location */
302 int nfx = fx, nfy = fy, /* new location */
303 d1, d2; /* shortest distances */
306 * It is possible that the monster at (fx,fy) is not the md when:
307 * the md rushed the hero and failed, and is now starting back.
309 if (m_at(fx, fy) == md) {
310 remove_monster(fx, fy); /* pick up from orig position */
315 * At the beginning and exit of this loop, md is not placed in the
319 /* Find a good location next to (fx,fy) closest to (tx,ty). */
320 d1 = dist2(fx, fy, tx, ty);
321 for (dx = -1; dx <= 1; dx++)
322 for (dy = -1; dy <= 1; dy++)
323 if ((dx || dy) && isok(fx + dx, fy + dy)
324 && !IS_STWALL(levl[fx + dx][fy + dy].typ)) {
325 d2 = dist2(fx + dx, fy + dy, tx, ty);
333 /* Break if the md couldn't find a new position. */
334 if (nfx == fx && nfy == fy)
337 fx = nfx; /* this is our new position */
340 /* Break if the md reaches its destination. */
341 if (fx == tx && fy == ty)
344 if ((mon = m_at(fx, fy)) != 0) /* save monster at this position */
345 verbalize1(md_exclamations());
346 else if (fx == u.ux && fy == u.uy)
348 verbalize("Excuse me.");
350 verbalize("
\82¿
\82å
\82Á
\82Æ
\82µ
\82Â
\82ê
\82¢
\81D");
352 place_monster(md, fx, fy); /* put md down */
353 newsym(fx, fy); /* see it */
354 flush_screen(0); /* make sure md shows up */
355 delay_output(); /* wait a little bit */
357 /* Remove md from the dungeon. Restore original mon, if necessary. */
359 if ((mon->mx != fx) || (mon->my != fy))
360 place_worm_seg(mon, fx, fy);
362 place_monster(mon, fx, fy);
364 remove_monster(fx, fy);
369 * Check for a monster at our stopping position (this is possible, but
370 * very unlikely). If one exists, then have the md leave in disgust.
372 if ((mon = m_at(fx, fy)) != 0) {
373 place_monster(md, fx, fy); /* display md with text below */
376 verbalize("This place's too crowded. I'm outta here.");
378 verbalize("
\82±
\82±
\82Í
\8d¬
\82Ý
\82·
\82¬
\81D
\82±
\82±
\82Å
\91Ò
\82Á
\82Ä
\82é
\82æ
\81D");
380 if ((mon->mx != fx) || (mon->my != fy)) /* put mon back */
381 place_worm_seg(mon, fx, fy);
383 place_monster(mon, fx, fy);
389 place_monster(md, fx, fy); /* place at final spot */
392 delay_output(); /* wait a little bit */
397 /* Deliver a scroll of mail. */
401 struct mail_info *info;
405 boolean message_seen = FALSE;
407 /* Try to find good starting and stopping places. */
408 if (!md_start(&start) || !md_stop(&stop, &start))
411 /* Make the daemon. Have it rush towards the hero. */
412 if (!(md = makemon(&mons[PM_MAIL_DAEMON], start.x, start.y, NO_MM_FLAGS)))
414 if (!md_rush(md, stop.x, stop.y))
419 verbalize("%s, %s! %s.", Hello(md), plname, info->display_txt);
421 verbalize("%s
\81I%s
\81D", Hello(md), info->display_txt);
424 if (info->message_typ) {
425 struct obj *obj = mksobj(SCR_MAIL, FALSE, FALSE);
427 if (info->object_nam)
428 obj = oname(obj, info->object_nam);
429 if (info->response_cmd)
430 new_omailcmd(obj, info->response_cmd);
432 if (distu(md->mx, md->my) > 2)
436 verbalize("
\82Ù
\82ç
\82æ
\81I");
437 display_nhwindow(WIN_MESSAGE, FALSE);
439 obj = hold_another_object(obj, "Oops!", (const char *) 0,
442 obj = hold_another_object(obj, "
\82¨
\82Á
\82Æ
\81I", (const char *) 0,
447 /* zip back to starting location */
449 (void) md_rush(md, start.x, start.y);
451 /* deliver some classes of messages even if no daemon ever shows up */
453 if (!message_seen && info->message_typ == MSG_OTHER)
455 pline("Hark! \"%s.\"", info->display_txt);
457 pline("
\81u%s
\81D
\81v
\82Æ
\8c¾
\82¤
\82±
\82Æ
\82¾
\81I", info->display_txt);
460 #if !defined(UNIX) && !defined(VMS)
465 if (u.uswallow || !flags.biff)
467 if (mustgetmail < 0) {
468 #if defined(AMIGA) || defined(MSDOS) || defined(TOS)
469 mustgetmail = (moves < 2000) ? (100 + rn2(2000)) : (2000 + rn2(3000));
473 if (--mustgetmail <= 0) {
474 static struct mail_info deliver = {
476 MSG_MAIL, "I have some mail for you", 0, 0
478 MSG_MAIL, "
\83\81\81[
\83\8b\82ð
\8e\9d\82Á
\82Ä
\82«
\82½
\82æ", 0, 0
488 struct obj *otmp UNUSED;
490 static char *junk[] = {
491 NULL, /* placeholder for "Report bugs to <devteam@nethack.org>.", */
493 "Please disregard previous letter.", "Welcome to NetHack.",
495 "
\91O
\82Ì
\83\81\81[
\83\8b\82Í
\96Y
\82ê
\82Ä
\82
\82¾
\82³
\82¢
\81D", "NetHack
\82Ö
\82æ
\82¤
\82±
\82»
\81I",
497 "Only Amiga makes it possible.", "CATS have all the answers.",
500 "This mail complies with the Yendorian Anti-Spam Act (YASA)",
502 "
\82±
\82Ì
\83\81\81[
\83\8b\82Í
\83C
\83F
\83\93\83_
\81[
\83X
\83p
\83\80\91Î
\8dô
\96@(YASA)
\82É
\8f\80\8b\92\82µ
\82Ä
\82¢
\82Ü
\82·
\81D",
504 "Please find enclosed a small token to represent your Owlbear",
506 "
\82 \82È
\82½
\82Ì
\83A
\83E
\83\8b\83x
\83A
\82ð
\95\
\8c»
\82·
\82é
\82½
\82ß
\82É
\93¯
\95\95\82µ
\82½
\8f¬
\82³
\82¢
\83g
\81[
\83N
\83\93\82ð
\92T
\82µ
\82Ä
\82
\82¾
\82³
\82¢",
508 "**FR33 P0T10N 0F FULL H34L1NG**",
510 "**
\8a®
\91S
\89ñ
\95\9c\82Ì
\96ò
\83v
\83\8c\83[
\83\93\83g**",
512 "Please return to sender (Asmodeus)",
514 "
\91\97\90M
\8eÒ(
\83A
\83X
\83\82\83f
\83E
\83X)
\82É
\91\97\82è
\95Ô
\82µ
\82Ä
\82
\82¾
\82³
\82¢",
516 "Buy a potion of gain level for only $19.99! Guaranteed to be blessed!",
518 "
\83\8c\83x
\83\8b\83A
\83b
\83v
\82Ì
\96ò
\82ª
\82½
\82Á
\82½
\82Ì1980
\89~!
\8fj
\95\9f\95Û
\8fØ!",
520 "Invitation: Visit the NetHack web site at http://www.nethack.org!"
522 "
\8fµ
\91Ò
\8fó: NetHack
\83E
\83F
\83u
\83T
\83C
\83g http://www.nethack.org
\82É
\97\88\82Ä
\82Ë!"
525 /* XXX replace with more general substitution code and add local
526 * contact message. Also use DEVTEAM_URL */
527 if (junk[0] == NULL) {
528 #define BUGS_FORMAT "Report bugs to <%s>."
529 /* +2 from '%s' suffices as substitute for usual +1 for terminator */
530 junk[0] = (char *) alloc(strlen(BUGS_FORMAT) + strlen(DEVTEAM_EMAIL));
531 Sprintf(junk[0], BUGS_FORMAT, DEVTEAM_EMAIL);
536 pline("Unfortunately you cannot see what it says.");
538 pline("
\8ec
\94O
\82È
\82ª
\82ç
\89½
\82Æ
\8f\91\82¢
\82Ä
\82 \82é
\82Ì
\82©
\8c©
\82é
\82±
\82Æ
\82ª
\82Å
\82«
\82È
\82¢
\81D");
541 pline("It reads: \"%s\"", junk[rn2(SIZE(junk))]);
543 pline("
\82»
\82ê
\82ð
\93Ç
\82ñ
\82¾
\81F\"%s\"", junk[rn2(SIZE(junk))]);
546 #endif /* !UNIX && !VMS */
553 ck_server_admin_msg();
555 if (!mailbox || u.uswallow || !flags.biff
557 || moves < laststattime + MAILCKFREQ
562 laststattime = moves;
563 if (stat(mailbox, &nmstat)) {
564 #ifdef PERMANENT_MAILBOX
565 pline("Cannot get status of MAIL=\"%s\" anymore.", mailbox);
570 } else if (nmstat.st_mtime > omstat.st_mtime) {
571 if (nmstat.st_size) {
572 static struct mail_info deliver = {
573 #ifndef NO_MAILREADER
575 MSG_MAIL, "I have some mail for you",
577 MSG_MAIL, "
\83\81\83C
\83\8b\82ð
\8e\9d\82Á
\82Ä
\82«
\82½
\82æ",
579 /* suppress creation and delivery of scroll of mail */
581 MSG_OTHER, "You have some mail in the outside world",
583 MSG_OTHER, "
\8aO
\82Ì
\90¢
\8aE
\82©
\82ç
\82Ì
\83\81\81[
\83\8b\82¾",
589 getmailstatus(); /* might be too late ... */
593 #if defined(SIMPLE_MAIL) || defined(SERVER_ADMIN_MSG)
595 read_simplemail(mbox, adminmsg)
599 FILE* mb = fopen(mbox, "r");
600 char curline[128], *msg;
601 boolean seen_one_already = FALSE;
603 struct flock fl = { 0 };
605 const char *msgfrom = adminmsg
607 ? "The voice of %s booms through the caverns:"
608 : "This message is from '%s'.";
610 ? "%s
\82Ì
\90º
\82ª
\93´
\8cA
\82É
\8b¿
\82«
\82í
\82½
\82Á
\82½:"
611 : "
\82±
\82ê
\82Í'%s'
\82©
\82ç
\82Ì
\83\81\83b
\83Z
\81[
\83W
\82¾
\81D";
619 fl.l_whence = SEEK_SET;
625 /* Allow this call to block. */
628 && fcntl (fileno (mb), F_SETLKW, &fl) == -1
633 while (fgets(curline, 128, mb) != NULL) {
637 fcntl (fileno(mb), F_UNLCK, &fl);
640 pline("There is a%s message on this scroll.",
641 seen_one_already ? "nother" : "");
643 pline("
\82±
\82Ì
\8aª
\95¨
\82É
\82Í%s
\83\81\83b
\83Z
\81[
\83W
\82ª
\82 \82é
\81D",
644 seen_one_already ? "
\82Ü
\82¾" : "");
647 msg = strchr(curline, ':');
654 msg[strlen(msg) - 1] = '\0'; /* kill newline */
656 pline(msgfrom, curline);
661 pline("It reads: \"%s\".", msg);
663 pline("
\82»
\82ê
\82ð
\93Ç
\82ñ
\82¾
\81F
\81u%s
\81v", msg);
665 seen_one_already = TRUE;
670 fcntl(fileno(mb), F_SETLKW, &fl);
678 fcntl(fileno(mb), F_UNLCK, &fl);
683 display_nhwindow(WIN_MESSAGE, TRUE);
688 /* bail out _professionally_ */
691 pline("It appears to be all gibberish.");
693 pline("
\82±
\82ê
\82Í
\82Ü
\82Á
\82½
\82
\82¿
\82ñ
\82Õ
\82ñ
\82©
\82ñ
\82Õ
\82ñ
\82¾
\81D");
695 #endif /* SIMPLE_MAIL */
698 ck_server_admin_msg()
700 #ifdef SERVER_ADMIN_MSG
701 static struct stat ost,nst;
702 static long lastchk = 0;
704 if (moves < lastchk + SERVER_ADMIN_MSG_CKFREQ) return;
707 if (!stat(SERVER_ADMIN_MSG, &nst)) {
708 if (nst.st_mtime > ost.st_mtime)
709 read_simplemail(SERVER_ADMIN_MSG, TRUE);
710 ost.st_mtime = nst.st_mtime;
712 #endif /* SERVER_ADMIN_MSG */
718 struct obj *otmp UNUSED;
720 #ifdef DEF_MAILREADER /* This implies that UNIX is defined */
721 register const char *mr = 0;
722 #endif /* DEF_MAILREADER */
724 read_simplemail(mailbox, FALSE);
726 #endif /* SIMPLE_MAIL */
727 #ifdef DEF_MAILREADER /* This implies that UNIX is defined */
728 display_nhwindow(WIN_MESSAGE, FALSE);
729 if (!(mr = nh_getenv("MAILREADER")))
733 (void) execl(mr, mr, (char *) 0);
734 nh_terminate(EXIT_FAILURE);
737 #ifndef AMS /* AMS mailboxes are directories */
738 display_file(mailbox, TRUE);
740 #endif /* DEF_MAILREADER */
742 /* get new stat; not entirely correct: there is a small time
743 window where we do not see new mail */
751 extern NDECL(struct mail_info *parse_next_broadcast);
753 volatile int broadcasts = 0;
758 struct mail_info *brdcst;
760 if (u.uswallow || !flags.biff)
763 while (broadcasts > 0) { /* process all trapped broadcasts [until] */
765 if ((brdcst = parse_next_broadcast()) != 0) {
767 break; /* only handle one real message at a time */
776 #ifdef SHELL /* can't access mail reader without spawning subprocess */
777 const char *txt, *cmd;
778 char *p, buf[BUFSZ] = DUMMY, qbuf[BUFSZ];
781 /* there should be a command in OMAILCMD */
787 if (has_omailcmd(otmp))
788 cmd = OMAILCMD(otmp);
792 Sprintf(qbuf, "System command (%s)", cmd);
794 if (*buf != '\033') {
795 for (p = eos(buf); p > buf; *p = '\0')
797 break; /* strip trailing spaces */
799 cmd = buf; /* use user entered command */
800 if (!strcmpi(cmd, "SPAWN") || !strcmp(cmd, "!"))
801 cmd = (char *) 0; /* interactive escape */
803 vms_doshell(cmd, TRUE);