NHDT->ANH, nethack->anethack, nhdat->anhdat
[aNetHack.git] / src / mail.c
blobe8a25aeddb4fae1c7ca63ad0f3e4e3528cd72bc3
1 /* aNetHack 0.0.1 mail.c $ANH-Date: 1464222344 2016/05/26 00:25:44 $ $ANH-Branch: master $:$ANH-Revision: 1.27 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* aNetHack may be freely redistributed. See license for details. */
5 #include "hack.h"
7 #ifdef MAIL
8 #ifdef SIMPLE_MAIL
9 # include <fcntl.h>
10 # include <errno.h>
11 #endif /* SIMPLE_MAIL */
12 #include "mail.h"
15 * Notify user when new mail has arrived. Idea by Merlyn Leroy.
17 * The mail daemon can move with less than usual restraint. It can:
18 * - move diagonally from a door
19 * - use secret and closed doors
20 * - run through a monster ("Gangway!", etc.)
21 * - run over pools & traps
23 * Possible extensions:
24 * - Open the file MAIL and do fstat instead of stat for efficiency.
25 * (But sh uses stat, so this cannot be too bad.)
26 * - Examine the mail and produce a scroll of mail named "From somebody".
27 * - Invoke MAILREADER in such a way that only this single mail is read.
28 * - Do something to the text when the scroll is enchanted or cancelled.
29 * - Make the daemon always appear at a stairwell, and have it find a
30 * path to the hero.
32 * Note by Olaf Seibert: On the Amiga, we usually don't get mail. So we go
33 * through most of the effects at 'random' moments.
34 * Note by Paul Winner: The MSDOS port also 'fakes' the mail daemon at
35 * random intervals.
38 STATIC_DCL boolean FDECL(md_start, (coord *));
39 STATIC_DCL boolean FDECL(md_stop, (coord *, coord *));
40 STATIC_DCL boolean FDECL(md_rush, (struct monst *, int, int));
41 STATIC_DCL void FDECL(newmail, (struct mail_info *));
43 extern char *viz_rmin, *viz_rmax; /* line-of-sight limits (vision.c) */
45 #if !defined(UNIX) && !defined(VMS)
46 int mustgetmail = -1;
47 #endif
49 #ifdef UNIX
50 #include <sys/stat.h>
51 #include <pwd.h>
52 /* DON'T trust all Unices to declare getpwuid() in <pwd.h> */
53 #if !defined(_BULL_SOURCE) && !defined(__sgi) && !defined(_M_UNIX)
54 #if !defined(SUNOS4) && !(defined(ULTRIX) && defined(__GNUC__))
55 /* DO trust all SVR4 to typedef uid_t in <sys/types.h> (probably to a long) */
56 #if defined(POSIX_TYPES) || defined(SVR4) || defined(HPUX)
57 extern struct passwd *FDECL(getpwuid, (uid_t));
58 #else
59 extern struct passwd *FDECL(getpwuid, (int));
60 #endif
61 #endif
62 #endif
63 static struct stat omstat, nmstat;
64 static char *mailbox = (char *) 0;
65 static long laststattime;
67 #if !defined(MAILPATH) && defined(AMS) /* Just a placeholder for AMS */
68 #define MAILPATH "/dev/null"
69 #endif
70 #if !defined(MAILPATH) && (defined(LINUX) || defined(__osf__))
71 #define MAILPATH "/var/spool/mail/"
72 #endif
73 #if !defined(MAILPATH) && defined(__FreeBSD__)
74 #define MAILPATH "/var/mail/"
75 #endif
76 #if !defined(MAILPATH) && (defined(BSD) || defined(ULTRIX))
77 #define MAILPATH "/usr/spool/mail/"
78 #endif
79 #if !defined(MAILPATH) && (defined(SYSV) || defined(HPUX))
80 #define MAILPATH "/usr/mail/"
81 #endif
83 void
84 free_maildata()
86 if (mailbox)
87 free((genericptr_t) mailbox), mailbox = (char *) 0;
90 void
91 getmailstatus()
93 char *emailbox;
94 if ((emailbox = nh_getenv("MAIL")) != 0) {
95 mailbox = (char *) alloc((unsigned) strlen(emailbox));
96 Strcpy(mailbox, emailbox);
98 if (!mailbox) {
99 #ifdef MAILPATH
100 #ifdef AMS
101 struct passwd ppasswd;
103 (void) memcpy(&ppasswd, getpwuid(getuid()), sizeof(struct passwd));
104 if (ppasswd.pw_dir) {
105 mailbox = (char *) alloc((unsigned) strlen(ppasswd.pw_dir)
106 + sizeof(AMS_MAILBOX));
107 Strcpy(mailbox, ppasswd.pw_dir);
108 Strcat(mailbox, AMS_MAILBOX);
109 } else
110 return;
111 #else
112 const char *pw_name = getpwuid(getuid())->pw_name;
113 mailbox = (char *) alloc(sizeof(MAILPATH) + strlen(pw_name));
114 Strcpy(mailbox, MAILPATH);
115 Strcat(mailbox, pw_name);
116 #endif /* AMS */
117 #else
118 return;
119 #endif
121 if (stat(mailbox, &omstat)) {
122 #ifdef PERMANENT_MAILBOX
123 pline("Cannot get status of MAIL=\"%s\".", mailbox);
124 mailbox = 0;
125 #else
126 omstat.st_mtime = 0;
127 #endif
130 #endif /* UNIX */
133 * Pick coordinates for a starting position for the mail daemon. Called
134 * from newmail() and newphone().
136 STATIC_OVL boolean
137 md_start(startp)
138 coord *startp;
140 coord testcc; /* scratch coordinates */
141 int row; /* current row we are checking */
142 int lax; /* if TRUE, pick a position in sight. */
143 int dd; /* distance to current point */
144 int max_distance; /* max distance found so far */
147 * If blind and not telepathic, then it doesn't matter what we pick ---
148 * the hero is not going to see it anyway. So pick a nearby position.
150 if (Blind && !Blind_telepat) {
151 if (!enexto(startp, u.ux, u.uy, (struct permonst *) 0))
152 return FALSE; /* no good positions */
153 return TRUE;
157 * Arrive at an up or down stairwell if it is in line of sight from the
158 * hero.
160 if (couldsee(upstair.sx, upstair.sy)) {
161 startp->x = upstair.sx;
162 startp->y = upstair.sy;
163 return TRUE;
165 if (couldsee(dnstair.sx, dnstair.sy)) {
166 startp->x = dnstair.sx;
167 startp->y = dnstair.sy;
168 return TRUE;
172 * Try to pick a location out of sight next to the farthest position away
173 * from the hero. If this fails, try again, just picking the farthest
174 * position that could be seen. What we really ought to be doing is
175 * finding a path from a stairwell...
177 * The arrays viz_rmin[] and viz_rmax[] are set even when blind. These
178 * are the LOS limits for each row.
180 lax = 0; /* be picky */
181 max_distance = -1;
182 retry:
183 for (row = 0; row < ROWNO; row++) {
184 if (viz_rmin[row] < viz_rmax[row]) {
185 /* There are valid positions on this row. */
186 dd = distu(viz_rmin[row], row);
187 if (dd > max_distance) {
188 if (lax) {
189 max_distance = dd;
190 startp->y = row;
191 startp->x = viz_rmin[row];
193 } else if (enexto(&testcc, (xchar) viz_rmin[row], row,
194 (struct permonst *) 0)
195 && !cansee(testcc.x, testcc.y)
196 && couldsee(testcc.x, testcc.y)) {
197 max_distance = dd;
198 *startp = testcc;
201 dd = distu(viz_rmax[row], row);
202 if (dd > max_distance) {
203 if (lax) {
204 max_distance = dd;
205 startp->y = row;
206 startp->x = viz_rmax[row];
208 } else if (enexto(&testcc, (xchar) viz_rmax[row], row,
209 (struct permonst *) 0)
210 && !cansee(testcc.x, testcc.y)
211 && couldsee(testcc.x, testcc.y)) {
212 max_distance = dd;
213 *startp = testcc;
219 if (max_distance < 0) {
220 if (!lax) {
221 lax = 1; /* just find a position */
222 goto retry;
224 return FALSE;
227 return TRUE;
231 * Try to choose a stopping point as near as possible to the starting
232 * position while still adjacent to the hero. If all else fails, try
233 * enexto(). Use enexto() as a last resort because enexto() chooses
234 * its point randomly, which is not what we want.
236 STATIC_OVL boolean
237 md_stop(stopp, startp)
238 coord *stopp; /* stopping position (we fill it in) */
239 coord *startp; /* starting position (read only) */
241 int x, y, distance, min_distance = -1;
243 for (x = u.ux - 1; x <= u.ux + 1; x++)
244 for (y = u.uy - 1; y <= u.uy + 1; y++) {
245 if (!isok(x, y) || (x == u.ux && y == u.uy))
246 continue;
248 if (accessible(x, y) && !MON_AT(x, y)) {
249 distance = dist2(x, y, startp->x, startp->y);
250 if (min_distance < 0 || distance < min_distance
251 || (distance == min_distance && rn2(2))) {
252 stopp->x = x;
253 stopp->y = y;
254 min_distance = distance;
259 /* If we didn't find a good spot, try enexto(). */
260 if (min_distance < 0 && !enexto(stopp, u.ux, u.uy, &mons[PM_MAIL_DAEMON]))
261 return FALSE;
263 return TRUE;
266 /* Let the mail daemon have a larger vocabulary. */
267 static NEARDATA const char *mail_text[] = { "Gangway!", "Look out!",
268 "Pardon me!" };
269 #define md_exclamations() (mail_text[rn2(3)])
272 * Make the mail daemon run through the dungeon. The daemon will run over
273 * any monsters that are in its path, but will replace them later. Return
274 * FALSE if the md gets stuck in a position where there is a monster. Return
275 * TRUE otherwise.
277 STATIC_OVL boolean
278 md_rush(md, tx, ty)
279 struct monst *md;
280 register int tx, ty; /* destination of mail daemon */
282 struct monst *mon; /* displaced monster */
283 register int dx, dy; /* direction counters */
284 int fx = md->mx, fy = md->my; /* current location */
285 int nfx = fx, nfy = fy, /* new location */
286 d1, d2; /* shortest distances */
289 * It is possible that the monster at (fx,fy) is not the md when:
290 * the md rushed the hero and failed, and is now starting back.
292 if (m_at(fx, fy) == md) {
293 remove_monster(fx, fy); /* pick up from orig position */
294 newsym(fx, fy);
298 * At the beginning and exit of this loop, md is not placed in the
299 * dungeon.
301 while (1) {
302 /* Find a good location next to (fx,fy) closest to (tx,ty). */
303 d1 = dist2(fx, fy, tx, ty);
304 for (dx = -1; dx <= 1; dx++)
305 for (dy = -1; dy <= 1; dy++)
306 if ((dx || dy) && isok(fx + dx, fy + dy)
307 && !IS_STWALL(levl[fx + dx][fy + dy].typ)) {
308 d2 = dist2(fx + dx, fy + dy, tx, ty);
309 if (d2 < d1) {
310 d1 = d2;
311 nfx = fx + dx;
312 nfy = fy + dy;
316 /* Break if the md couldn't find a new position. */
317 if (nfx == fx && nfy == fy)
318 break;
320 fx = nfx; /* this is our new position */
321 fy = nfy;
323 /* Break if the md reaches its destination. */
324 if (fx == tx && fy == ty)
325 break;
327 if ((mon = m_at(fx, fy)) != 0) /* save monster at this position */
328 verbalize1(md_exclamations());
329 else if (fx == u.ux && fy == u.uy)
330 verbalize("Excuse me.");
332 place_monster(md, fx, fy); /* put md down */
333 newsym(fx, fy); /* see it */
334 flush_screen(0); /* make sure md shows up */
335 delay_output(); /* wait a little bit */
337 /* Remove md from the dungeon. Restore original mon, if necessary. */
338 if (mon) {
339 if ((mon->mx != fx) || (mon->my != fy))
340 place_worm_seg(mon, fx, fy);
341 else
342 place_monster(mon, fx, fy);
343 } else
344 remove_monster(fx, fy);
345 newsym(fx, fy);
349 * Check for a monster at our stopping position (this is possible, but
350 * very unlikely). If one exists, then have the md leave in disgust.
352 if ((mon = m_at(fx, fy)) != 0) {
353 place_monster(md, fx, fy); /* display md with text below */
354 newsym(fx, fy);
355 verbalize("This place's too crowded. I'm outta here.");
357 if ((mon->mx != fx) || (mon->my != fy)) /* put mon back */
358 place_worm_seg(mon, fx, fy);
359 else
360 place_monster(mon, fx, fy);
362 newsym(fx, fy);
363 return FALSE;
366 place_monster(md, fx, fy); /* place at final spot */
367 newsym(fx, fy);
368 flush_screen(0);
369 delay_output(); /* wait a little bit */
371 return TRUE;
374 /* Deliver a scroll of mail. */
375 /*ARGSUSED*/
376 STATIC_OVL void
377 newmail(info)
378 struct mail_info *info;
380 struct monst *md;
381 coord start, stop;
382 boolean message_seen = FALSE;
384 /* Try to find good starting and stopping places. */
385 if (!md_start(&start) || !md_stop(&stop, &start))
386 goto give_up;
388 /* Make the daemon. Have it rush towards the hero. */
389 if (!(md = makemon(&mons[PM_MAIL_DAEMON], start.x, start.y, NO_MM_FLAGS)))
390 goto give_up;
391 if (!md_rush(md, stop.x, stop.y))
392 goto go_back;
394 message_seen = TRUE;
395 verbalize("%s, %s! %s.", Hello(md), plname, info->display_txt);
397 if (info->message_typ) {
398 struct obj *obj = mksobj(SCR_MAIL, FALSE, FALSE);
400 if (info->object_nam)
401 obj = oname(obj, info->object_nam);
402 if (info->response_cmd)
403 new_omailcmd(obj, info->response_cmd);
405 if (distu(md->mx, md->my) > 2)
406 verbalize("Catch!");
407 display_nhwindow(WIN_MESSAGE, FALSE);
408 obj = hold_another_object(obj, "Oops!", (const char *) 0,
409 (const char *) 0);
412 /* zip back to starting location */
413 go_back:
414 (void) md_rush(md, start.x, start.y);
415 mongone(md);
416 /* deliver some classes of messages even if no daemon ever shows up */
417 give_up:
418 if (!message_seen && info->message_typ == MSG_OTHER)
419 pline("Hark! \"%s.\"", info->display_txt);
422 #if !defined(UNIX) && !defined(VMS)
424 void
425 ckmailstatus()
427 if (u.uswallow || !flags.biff)
428 return;
429 if (mustgetmail < 0) {
430 #if defined(AMIGA) || defined(MSDOS) || defined(TOS)
431 mustgetmail = (moves < 2000) ? (100 + rn2(2000)) : (2000 + rn2(3000));
432 #endif
433 return;
435 if (--mustgetmail <= 0) {
436 static struct mail_info deliver = {
437 MSG_MAIL, "I have some mail for you", 0, 0
439 newmail(&deliver);
440 mustgetmail = -1;
444 /*ARGSUSED*/
445 void
446 readmail(otmp)
447 struct obj *otmp UNUSED;
449 static char *junk[] = {
450 NULL, /* placeholder for "Report bugs to <devteam@anethack.org>.", */
451 "Please disregard previous letter.", "Welcome to aNetHack.",
452 #ifdef AMIGA
453 "Only Amiga makes it possible.", "CATS have all the answers.",
454 #endif
455 "This mail complies with the Yendorian Anti-Spam Act (YASA)",
456 "Please find enclosed a small token to represent your Owlbear",
457 "**FR33 P0T10N 0F FULL H34L1NG**",
458 "Please return to sender (Asmodeus)",
459 "Buy a potion of gain level for only $19.99! Guaranteed to be blessed!",
460 "Invitation: Visit the aNetHack web site at http://www.anethack.org!"
463 /* XXX replace with more general substitution code and add local
464 * contact message. Also use DEVTEAM_URL */
465 if (junk[0] == NULL) {
466 #define BUGS_FORMAT "Report bugs to <%s>."
467 /* +2 from '%s' suffices as substitute for usual +1 for terminator */
468 junk[0] = (char *) alloc(strlen(BUGS_FORMAT) + strlen(DEVTEAM_EMAIL));
469 Sprintf(junk[0], BUGS_FORMAT, DEVTEAM_EMAIL);
470 #undef BUGS_FORMAT
472 if (Blind) {
473 pline("Unfortunately you cannot see what it says.");
474 } else
475 pline("It reads: \"%s\"", junk[rn2(SIZE(junk))]);
478 #endif /* !UNIX && !VMS */
480 #ifdef UNIX
482 void
483 ckmailstatus()
485 ck_server_admin_msg();
487 if (!mailbox || u.uswallow || !flags.biff
488 #ifdef MAILCKFREQ
489 || moves < laststattime + MAILCKFREQ
490 #endif
492 return;
494 laststattime = moves;
495 if (stat(mailbox, &nmstat)) {
496 #ifdef PERMANENT_MAILBOX
497 pline("Cannot get status of MAIL=\"%s\" anymore.", mailbox);
498 mailbox = 0;
499 #else
500 nmstat.st_mtime = 0;
501 #endif
502 } else if (nmstat.st_mtime > omstat.st_mtime) {
503 if (nmstat.st_size) {
504 static struct mail_info deliver = {
505 #ifndef NO_MAILREADER
506 MSG_MAIL, "I have some mail for you",
507 #else
508 /* suppress creation and delivery of scroll of mail */
509 MSG_OTHER, "You have some mail in the outside world",
510 #endif
511 0, 0
513 newmail(&deliver);
515 getmailstatus(); /* might be too late ... */
519 #if defined(SIMPLE_MAIL) || defined(SERVER_ADMIN_MSG)
520 void
521 read_simplemail(mbox, adminmsg)
522 char *mbox;
523 boolean adminmsg;
525 FILE* mb = fopen(mbox, "r");
526 char curline[128], *msg;
527 boolean seen_one_already = FALSE;
528 #ifdef SIMPLE_MAIL
529 struct flock fl = { 0 };
530 #endif
531 const char *msgfrom = adminmsg
532 ? "The voice of %s booms through the caverns:"
533 : "This message is from '%s'.";
535 if (!mb)
536 goto bail;
538 #ifdef SIMPLE_MAIL
539 fl.l_type = F_RDLCK;
540 fl.l_whence = SEEK_SET;
541 fl.l_start = 0;
542 fl.l_len = 0;
543 errno = 0;
544 #endif
546 /* Allow this call to block. */
547 if (!adminmsg
548 #ifdef SIMPLE_MAIL
549 && fcntl (fileno (mb), F_SETLKW, &fl) == -1
550 #endif
552 goto bail;
554 while (fgets(curline, 128, mb) != NULL) {
555 if (!adminmsg) {
556 #ifdef SIMPLE_MAIL
557 fl.l_type = F_UNLCK;
558 fcntl (fileno(mb), F_UNLCK, &fl);
559 #endif
560 pline("There is a%s message on this scroll.",
561 seen_one_already ? "nother" : "");
563 msg = strchr(curline, ':');
565 if (!msg)
566 goto bail;
568 *msg = '\0';
569 msg++;
570 msg[strlen(msg) - 1] = '\0'; /* kill newline */
572 pline(msgfrom, curline);
573 if (adminmsg)
574 verbalize(msg);
575 else
576 pline("It reads: \"%s\".", msg);
578 seen_one_already = TRUE;
579 #ifdef SIMPLE_MAIL
580 errno = 0;
581 if (!adminmsg) {
582 fl.l_type = F_RDLCK;
583 fcntl(fileno(mb), F_SETLKW, &fl);
585 #endif
588 #ifdef SIMPLE_MAIL
589 if (!adminmsg) {
590 fl.l_type = F_UNLCK;
591 fcntl(fileno(mb), F_UNLCK, &fl);
593 #endif
594 fclose(mb);
595 if (adminmsg)
596 display_nhwindow(WIN_MESSAGE, TRUE);
597 else
598 unlink(mailbox);
599 return;
600 bail:
601 /* bail out _professionally_ */
602 if (!adminmsg)
603 pline("It appears to be all gibberish.");
605 #endif /* SIMPLE_MAIL */
607 void
608 ck_server_admin_msg()
610 #ifdef SERVER_ADMIN_MSG
611 static struct stat ost,nst;
612 static long lastchk = 0;
614 if (moves < lastchk + SERVER_ADMIN_MSG_CKFREQ) return;
615 lastchk = moves;
617 if (!stat(SERVER_ADMIN_MSG, &nst)) {
618 if (nst.st_mtime > ost.st_mtime)
619 read_simplemail(SERVER_ADMIN_MSG, TRUE);
620 ost.st_mtime = nst.st_mtime;
622 #endif /* SERVER_ADMIN_MSG */
625 /*ARGSUSED*/
626 void
627 readmail(otmp)
628 struct obj *otmp UNUSED;
630 #ifdef DEF_MAILREADER /* This implies that UNIX is defined */
631 register const char *mr = 0;
632 #endif /* DEF_MAILREADER */
633 #ifdef SIMPLE_MAIL
634 read_simplemail(mailbox, FALSE);
635 return;
636 #endif /* SIMPLE_MAIL */
637 #ifdef DEF_MAILREADER /* This implies that UNIX is defined */
638 display_nhwindow(WIN_MESSAGE, FALSE);
639 if (!(mr = nh_getenv("MAILREADER")))
640 mr = DEF_MAILREADER;
642 if (child(1)) {
643 (void) execl(mr, mr, (char *) 0);
644 terminate(EXIT_FAILURE);
646 #else
647 #ifndef AMS /* AMS mailboxes are directories */
648 display_file(mailbox, TRUE);
649 #endif /* AMS */
650 #endif /* DEF_MAILREADER */
652 /* get new stat; not entirely correct: there is a small time
653 window where we do not see new mail */
654 getmailstatus();
657 #endif /* UNIX */
659 #ifdef VMS
661 extern NDECL(struct mail_info *parse_next_broadcast);
663 volatile int broadcasts = 0;
665 void
666 ckmailstatus()
668 struct mail_info *brdcst;
670 if (u.uswallow || !flags.biff)
671 return;
673 while (broadcasts > 0) { /* process all trapped broadcasts [until] */
674 broadcasts--;
675 if ((brdcst = parse_next_broadcast()) != 0) {
676 newmail(brdcst);
677 break; /* only handle one real message at a time */
682 void
683 readmail(otmp)
684 struct obj *otmp;
686 #ifdef SHELL /* can't access mail reader without spawning subprocess */
687 const char *txt, *cmd;
688 char *p, buf[BUFSZ], qbuf[BUFSZ];
689 int len;
691 /* there should be a command in OMAILCMD */
692 if (has_oname(otmp))
693 txt = ONAME(otmp);
694 else
695 txt = "";
696 len = strlen(txt);
697 if (has_omailcmd(otmp))
698 cmd = OMAILCMD(otmp);
699 if (!cmd || !*cmd)
700 cmd = "SPAWN";
702 Sprintf(qbuf, "System command (%s)", cmd);
703 getlin(qbuf, buf);
704 if (*buf != '\033') {
705 for (p = eos(buf); p > buf; *p = '\0')
706 if (*--p != ' ')
707 break; /* strip trailing spaces */
708 if (*buf)
709 cmd = buf; /* use user entered command */
710 if (!strcmpi(cmd, "SPAWN") || !strcmp(cmd, "!"))
711 cmd = (char *) 0; /* interactive escape */
713 vms_doshell(cmd, TRUE);
714 (void) sleep(1);
716 #else
717 nhUse(otmp);
718 #endif /* SHELL */
721 #endif /* VMS */
722 #endif /* MAIL */
724 /*mail.c*/