Blindfold removal fix
[slashemextended.git] / src / mail.c
blob30d07efecf12e16b00a027dad31c4d3828b56fcc
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. */
5 #include "hack.h"
7 #ifdef MAIL
8 #include <fcntl.h>
9 #include <errno.h>
10 #include "mail.h"
13 * Notify user when new mail has arrived. Idea by Merlyn Leroy.
15 * The mail daemon can move with less than usual restraint. It can:
16 * - move diagonally from a door
17 * - use secret and closed doors
18 * - run through a monster ("Gangway!", etc.)
19 * - run over pools & traps
21 * Possible extensions:
22 * - Open the file MAIL and do fstat instead of stat for efficiency.
23 * (But sh uses stat, so this cannot be too bad.)
24 * - Examine the mail and produce a scroll of mail named "From somebody".
25 * - Invoke MAILREADER in such a way that only this single letter is read.
26 * - Do something to the text when the scroll is enchanted or cancelled.
27 * - Make the daemon always appear at a stairwell, and have it find a
28 * path to the hero.
30 * Note by Olaf Seibert: On the Amiga, we usually don't get mail. So we go
31 * through most of the effects at 'random' moments.
32 * Note by Paul Winner: The MSDOS port also 'fakes' the mail daemon at
33 * random intervals.
36 STATIC_DCL boolean md_start(coord *);
37 STATIC_DCL boolean md_stop(coord *, coord *);
38 STATIC_DCL boolean md_rush(struct monst *,int,int);
39 STATIC_DCL void newmail(struct mail_info *);
41 int mailckfreq = 0;
43 extern char *viz_rmin, *viz_rmax; /* line-of-sight limits (vision.c) */
45 #ifdef OVL0
47 # if !defined(UNIX) && !defined(VMS) && !defined(LAN_MAIL)
48 int mustgetmail = -1;
49 # endif
51 #endif /* OVL0 */
52 #ifdef OVLB
54 # ifdef UNIX
55 #include <sys/stat.h>
56 #include <pwd.h>
57 /* DON'T trust all Unices to declare getpwuid() in <pwd.h> */
58 # if !defined(_BULL_SOURCE) && !defined(__sgi) && !defined(_M_UNIX)
59 # if !defined(SUNOS4) && !(defined(ULTRIX) && defined(__GNUC__))
60 # if !defined(LINUX)
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 *getpwuid(uid_t);
64 # else
65 extern struct passwd *getpwuid(int);
66 # endif
67 # endif
68 # endif
69 # endif
70 static struct stat omstat,nmstat;
71 static char *mailbox = (char *)0;
72 static long laststattime;
74 # if !defined(MAILPATH) && defined(AMS) /* Just a placeholder for AMS */
75 # define MAILPATH "/dev/null"
76 # endif
77 # if !defined(MAILPATH) && (defined(LINUX) || defined(__osf__))
78 # define MAILPATH "/var/spool/mail/"
79 # endif
80 # if !defined(MAILPATH) && defined(__FreeBSD__)
81 # define MAILPATH "/var/mail/"
82 # endif
83 # if !defined(MAILPATH) && (defined(BSD) || defined(ULTRIX))
84 # define MAILPATH "/usr/spool/mail/"
85 # endif
86 # if !defined(MAILPATH) && (defined(SYSV) || defined(HPUX))
87 # define MAILPATH "/usr/mail/"
88 # endif
90 void
91 getmailstatus()
93 if(!mailbox && !(mailbox = nh_getenv("MAIL"))) {
94 # if defined(MAILPATH) && !defined(PUBLIC_SERVER)
95 # ifdef AMS
96 struct passwd ppasswd;
98 (void) memcpy(&ppasswd, getpwuid(getuid()), sizeof(struct passwd));
99 if (ppasswd.pw_dir) {
100 mailbox = (char *) alloc((unsigned) strlen(ppasswd.pw_dir)+sizeof(AMS_MAILBOX));
101 strcpy(mailbox, ppasswd.pw_dir);
102 strcat(mailbox, AMS_MAILBOX);
103 } else
104 return;
105 # else
106 const char *pw_name = getpwuid(getuid())->pw_name;
107 mailbox = (char *) alloc(sizeof(MAILPATH)+strlen(pw_name));
108 strcpy(mailbox, MAILPATH);
109 strcat(mailbox, pw_name);
110 # endif /* AMS */
111 # else
112 return;
113 # endif
115 if(stat(mailbox, &omstat)){
116 # ifdef PERMANENT_MAILBOX
117 pline("Cannot get status of MAIL=\"%s\".", mailbox);
118 mailbox = 0;
119 # else
120 omstat.st_mtime = 0;
121 # endif
124 # endif /* UNIX */
126 #endif /* OVLB */
127 #ifdef OVL0
130 * Pick coordinates for a starting position for the mail daemon. Called
131 * from newmail() and newphone().
133 STATIC_OVL boolean
134 md_start(startp)
135 coord *startp;
137 coord testcc; /* scratch coordinates */
138 int row; /* current row we are checking */
139 int lax; /* if TRUE, pick a position in sight. */
140 int dd; /* distance to current point */
141 int max_distance; /* max distance found so far */
144 * If blind and not telepathic, then it doesn't matter what we pick ---
145 * the hero is not going to see it anyway. So pick a nearby position.
147 if (Blind && !Blind_telepat) {
148 if (!enexto(startp, u.ux, u.uy, (struct permonst *) 0))
149 return FALSE; /* no good posiitons */
150 return TRUE;
154 * Arrive at an up or down stairwell if it is in line of sight from the
155 * hero.
157 if (couldsee(upstair.sx, upstair.sy)) {
158 startp->x = upstair.sx;
159 startp->y = upstair.sy;
160 return TRUE;
162 if (couldsee(dnstair.sx, dnstair.sy)) {
163 startp->x = dnstair.sx;
164 startp->y = dnstair.sy;
165 return TRUE;
169 * Try to pick a location out of sight next to the farthest position away
170 * from the hero. If this fails, try again, just picking the farthest
171 * position that could be seen. What we really ought to be doing is
172 * finding a path from a stairwell...
174 * The arrays viz_rmin[] and viz_rmax[] are set even when blind. These
175 * are the LOS limits for each row.
177 lax = 0; /* be picky */
178 max_distance = -1;
179 retry:
180 for (row = 0; row < ROWNO; row++) {
181 if (viz_rmin[row] < viz_rmax[row]) {
182 /* There are valid positions on this row. */
183 dd = distu(viz_rmin[row],row);
184 if (dd > max_distance) {
185 if (lax) {
186 max_distance = dd;
187 startp->y = row;
188 startp->x = viz_rmin[row];
190 } else if (enexto(&testcc, (xchar)viz_rmin[row], row,
191 (struct permonst *) 0) &&
192 !cansee(testcc.x, testcc.y) &&
193 couldsee(testcc.x, testcc.y)) {
194 max_distance = dd;
195 *startp = testcc;
198 dd = distu(viz_rmax[row],row);
199 if (dd > max_distance) {
200 if (lax) {
201 max_distance = dd;
202 startp->y = row;
203 startp->x = viz_rmax[row];
205 } else if (enexto(&testcc, (xchar)viz_rmax[row], row,
206 (struct permonst *) 0) &&
207 !cansee(testcc.x,testcc.y) &&
208 couldsee(testcc.x, testcc.y)) {
210 max_distance = dd;
211 *startp = testcc;
217 if (max_distance < 0) {
218 if (!lax) {
219 lax = 1; /* just find a position */
220 goto retry;
222 return FALSE;
225 return TRUE;
229 * Try to choose a stopping point as near as possible to the starting
230 * position while still adjacent to the hero. If all else fails, try
231 * enexto(). Use enexto() as a last resort because enexto() chooses
232 * its point randomly, which is not what we want.
234 STATIC_OVL boolean
235 md_stop(stopp, startp)
236 coord *stopp; /* stopping position (we fill it in) */
237 coord *startp; /* starting positon (read only) */
239 int x, y, distance, min_distance = -1;
241 for (x = u.ux-1; x <= u.ux+1; x++)
242 for (y = u.uy-1; y <= u.uy+1; y++) {
243 if (!isok(x, y) || (x == u.ux && y == u.uy)) continue;
245 if (ACCESSIBLE(levl[x][y].typ) && !MON_AT(x,y)) {
246 distance = dist2(x,y,startp->x,startp->y);
247 if (min_distance < 0 || distance < min_distance ||
248 (distance == min_distance && rn2(2))) {
249 stopp->x = x;
250 stopp->y = y;
251 min_distance = distance;
256 /* If we didn't find a good spot, try enexto(). */
257 if (min_distance < 0 &&
258 !enexto(stopp, u.ux, u.uy, &mons[PM_MAIL_DAEMON]))
259 return FALSE;
261 return TRUE;
264 /* Let the mail daemon have a larger vocabulary. */
265 static NEARDATA const char *mail_text[] = {
266 "Gangway!",
267 "Look out!",
268 "Pardon me!"
270 #define md_exclamations() (mail_text[rn2(3)])
273 * Make the mail daemon run through the dungeon. The daemon will run over
274 * any monsters that are in its path, but will replace them later. Return
275 * FALSE if the md gets stuck in a position where there is a monster. Return
276 * TRUE otherwise.
278 STATIC_OVL boolean
279 md_rush(md,tx,ty)
280 struct monst *md;
281 register int tx, ty; /* destination of mail daemon */
283 struct monst *mon; /* displaced monster */
284 register int dx, dy; /* direction counters */
285 int fx = md->mx, fy = md->my; /* current location */
286 int nfx = fx, nfy = fy, /* new location */
287 d1, d2; /* shortest distances */
290 * It is possible that the monster at (fx,fy) is not the md when:
291 * the md rushed the hero and failed, and is now starting back.
293 if (m_at(fx, fy) == md) {
294 remove_monster(fx, fy); /* pick up from orig position */
295 newsym(fx, fy);
299 * At the beginning and exit of this loop, md is not placed in the
300 * dungeon.
302 while (1) {
303 /* Find a good location next to (fx,fy) closest to (tx,ty). */
304 d1 = dist2(fx,fy,tx,ty);
305 for (dx = -1; dx <= 1; dx++) for(dy = -1; dy <= 1; dy++)
306 if ((dx || dy) && isok(fx+dx,fy+dy) && !IS_WATERTUNNEL(levl[fx+dx][fy+dy].typ) &&
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) break;
319 fx = nfx; /* this is our new position */
320 fy = nfy;
322 /* Break if the md reaches its destination. */
323 if (fx == tx && fy == ty) break;
325 if ((mon = m_at(fx,fy)) != 0) /* save monster at this position */
326 verbalize("%s", md_exclamations());
327 else if (fx == u.ux && fy == u.uy)
328 verbalize("Excuse me.");
330 place_monster(md,fx,fy); /* put md down */
331 newsym(fx,fy); /* see it */
332 flush_screen(0); /* make sure md shows up */
333 delay_output(); /* wait a little bit */
335 /* Remove md from the dungeon. Restore original mon, if necessary. */
336 if (mon) {
337 if ((mon->mx != fx) || (mon->my != fy))
338 place_worm_seg(mon, fx, fy);
339 else
340 place_monster(mon, fx, fy);
341 } else
342 remove_monster(fx, fy);
343 newsym(fx,fy);
347 * Check for a monster at our stopping position (this is possible, but
348 * very unlikely). If one exists, then have the md leave in disgust.
350 if ((mon = m_at(fx, fy)) != 0) {
351 place_monster(md, fx, fy); /* display md with text below */
352 newsym(fx, fy);
353 verbalize("This place's too crowded. I'm outta here.");
355 if ((mon->mx != fx) || (mon->my != fy)) /* put mon back */
356 place_worm_seg(mon, fx, fy);
357 else
358 place_monster(mon, fx, fy);
360 newsym(fx, fy);
361 return FALSE;
364 place_monster(md, fx, fy); /* place at final spot */
365 newsym(fx, fy);
366 flush_screen(0);
367 delay_output(); /* wait a little bit */
369 return TRUE;
372 /* Deliver a scroll of mail. */
373 /*ARGSUSED*/
374 STATIC_OVL void
375 newmail(info)
376 struct mail_info *info;
378 struct monst *md;
379 coord start, stop;
380 boolean message_seen = FALSE;
382 /* Try to find good starting and stopping places. */
383 if (!md_start(&start) || !md_stop(&stop,&start)) goto give_up;
385 /* Make the daemon. Have it rush towards the hero. */
386 if (!(md = makemon(&mons[PM_MAIL_DAEMON], start.x, start.y, NO_MM_FLAGS)))
387 goto give_up;
388 if (!md_rush(md, stop.x, stop.y)) goto go_back;
390 message_seen = TRUE;
391 verbalize("%s, %s! %s.", Hello(md), playeraliasname, info->display_txt);
394 if (info->message_typ) {
395 struct obj *obj = mksobj(SCR_MAIL, FALSE, FALSE, FALSE);
396 if (!obj) return;
398 /* In game master mode, mail from me will spawn monsters. Read it immediately, so you can't avoid them :P --Amy */
399 #ifdef GMMODE
400 if (flags.gmmode || flags.supergmmode) readmail(obj);
401 #endif
402 if (distu(md->mx,md->my) > 2)
403 verbalize("Catch!");
404 display_nhwindow(WIN_MESSAGE, FALSE);
405 if (info->object_nam) {
406 obj = oname(obj, info->object_nam);
407 if (info->response_cmd) { /*(hide extension of the obj name)*/
408 int namelth = info->response_cmd - info->object_nam - 1;
409 if ( namelth <= 0 || namelth >= (int) obj->onamelth )
410 impossible("mail delivery screwed up");
411 else
412 *(ONAME(obj) + namelth) = '\0';
413 /* Note: renaming object will discard the hidden command. */
416 obj = hold_another_object(obj, "Oops!",
417 (const char *)0, (const char *)0);
420 /* zip back to starting location */
421 go_back:
422 (void) md_rush(md, start.x, start.y);
423 mongone(md);
424 /* deliver some classes of messages even if no daemon ever shows up */
425 give_up:
426 if (!message_seen && info->message_typ == MSG_OTHER)
427 pline("Hark! \"%s.\"", info->display_txt);
430 # if !defined(UNIX) && !defined(VMS) && !defined(LAN_MAIL)
432 void
433 ckmailstatus()
435 if (u.uswallow || !flags.biff) return;
436 if (mustgetmail < 0) {
437 #if defined(AMIGA) || defined(MSDOS) || defined(TOS) || defined(OS2)
438 mustgetmail=(moves<2000)?(100+rn2(2000)):(2000+rn2(3000));
439 #endif
440 return;
442 if (--mustgetmail <= 0) {
443 static struct mail_info
444 deliver = {MSG_MAIL,"I have some mail for you",0,0};
445 newmail(&deliver);
446 mustgetmail = -1;
450 /*ARGSUSED*/
451 void
452 readmail(otmp)
453 struct obj *otmp;
455 const char *line;
456 char buf[BUFSZ];
457 line = getrumor(bcsign(otmp), buf, TRUE);
458 if (!*line)
459 line = "NetHack rumors file closed for renovation.";
460 if (Blind) {
461 pline("Unfortunately you cannot see what it says.");
462 } else
463 pline("It reads: \"%s\"", line);
467 # endif /* !UNIX && !VMS */
469 # ifdef UNIX
471 void
472 ckmailstatus()
474 if (iflags.debug_fuzzer) return;
476 #ifdef SIMPLE_MAIL
477 if (mailckfreq == 0)
478 mailckfreq = (iflags.simplemail ? 5 : 10);
479 #else
480 mailckfreq = 10;
481 #endif
483 if(!mailbox || u.uswallow || !flags.biff
484 || moves < laststattime + mailckfreq)
485 return;
487 laststattime = moves;
488 if(stat(mailbox, &nmstat)){
489 # ifdef PERMANENT_MAILBOX
490 pline("Cannot get status of MAIL=\"%s\" anymore.", mailbox);
491 mailbox = 0;
492 # else
493 nmstat.st_mtime = 0;
494 # endif
495 } else if(nmstat.st_mtime > omstat.st_mtime) {
496 if (nmstat.st_size) {
497 static struct mail_info deliver = {
498 # ifndef NO_MAILREADER
499 MSG_MAIL, "I have some mail for you",
500 # else
501 /* suppress creation and delivery of scroll of mail */
502 MSG_OTHER, "You have some mail in the outside world",
503 # endif
504 0, 0
506 newmail(&deliver);
508 getmailstatus(); /* might be too late ... */
512 /*ARGSUSED*/
513 void
514 readmail(otmp)
515 struct obj *otmp;
517 #ifdef DEF_MAILREADER
518 register const char *mr = 0;
519 #endif /* DEF_MAILREADER */
520 #ifdef SIMPLE_MAIL
521 if (iflags.simplemail)
523 FILE* mb = fopen(mailbox, "r");
524 char curline[102], *msg;
525 boolean seen_one_already = FALSE;
526 struct flock fl = { 0 };
528 fl.l_type = F_RDLCK;
529 fl.l_whence = SEEK_SET;
530 fl.l_start = 0;
531 fl.l_len = 0;
533 if (!mb)
534 goto bail;
536 /* Allow this call to block. */
537 if (fcntl (fileno (mb), F_SETLKW, &fl) == -1)
538 goto bail;
540 errno = 0;
542 while (fgets(curline, 102, mb) != NULL)
544 fl.l_type = F_UNLCK;
545 fcntl (fileno(mb), F_UNLCK, &fl);
547 pline("There is a%s message on this scroll.",
548 seen_one_already ? "nother" : "");
550 msg = strchr(curline, ':');
552 if (!msg)
553 goto bail;
555 *msg = '\0';
556 msg++;
558 pline ("This message is from '%s'.", curline);
560 #ifdef GMMODE
561 if ( (flags.gmmode || flags.supergmmode) && !(flags.gmmessage)) goto dontread;
562 #endif
563 msg[strlen(msg) - 1] = '\0'; /* kill newline */
564 pline ("It reads: \"%s\".", msg);
566 dontread:
568 /* Game master mode: if active, I (Amy) can send you mail to spawn monsters!
569 * If you turned off the gmmessage option, the monster I selected will be a surprise :) */
571 #ifdef GMMODE
572 if (flags.gmmode && !flags.supergmmode && strlen(curline) == 7 && !strncmpi(curline, "AmyBSOD", 8)) {
573 if (strlen(msg) > 9 && !strncmpi(msg, "genesis ", 8)) {
574 u.gmmailsreceived++;
575 gmmode_genesis(msg + 8);
578 if (flags.supergmmode) {
579 if (strlen(msg) > 9 && !strncmpi(msg, "genesis ", 8)) {
580 u.gmmailsreceived++;
581 gmmode_genesis(msg + 8);
584 #endif
585 seen_one_already = TRUE;
586 errno = 0;
588 fl.l_type = F_RDLCK;
589 fcntl(fileno(mb), F_SETLKW, &fl);
592 fl.l_type = F_UNLCK;
593 fcntl(fileno(mb), F_UNLCK, &fl);
595 fclose(mb);
596 unlink(mailbox);
597 return;
599 # endif /* SIMPLE_MAIL */
600 # ifdef DEF_MAILREADER /* This implies that UNIX is defined */
601 display_nhwindow(WIN_MESSAGE, FALSE);
602 if(!(mr = nh_getenv("MAILREADER")))
603 mr = DEF_MAILREADER;
605 if(child(1)){
606 (void) execl(mr, mr, (char *)0);
607 terminate(EXIT_FAILURE);
609 # else
610 # ifndef AMS /* AMS mailboxes are directories */
611 display_file_area(NULL, mailbox, TRUE);
612 # endif /* AMS */
613 # endif /* DEF_MAILREADER */
615 /* get new stat; not entirely correct: there is a small time
616 window where we do not see new mail */
617 getmailstatus();
618 return;
620 #ifdef SIMPLE_MAIL
621 bail:
622 pline("It appears to be all gibberish."); /* bail out _professionally_ */
623 #endif
626 # endif /* UNIX */
628 # ifdef VMS
630 extern struct mail_info *parse_next_broadcast(void);
632 volatile int broadcasts = 0;
634 void
635 ckmailstatus()
637 struct mail_info *brdcst;
639 if(u.uswallow || !flags.biff) return;
641 while (broadcasts > 0) { /* process all trapped broadcasts [until] */
642 broadcasts--;
643 if ((brdcst = parse_next_broadcast()) != 0) {
644 newmail(brdcst);
645 break; /* only handle one real message at a time */
650 void
651 readmail(otmp)
652 struct obj *otmp;
654 # ifdef SHELL /* can't access mail reader without spawning subprocess */
655 const char *txt, *cmd;
656 char *p, buf[BUFSZ], qbuf[BUFSZ];
657 int len;
659 /* there should be a command hidden beyond the object name */
660 txt = otmp->onamelth ? ONAME(otmp) : "";
661 len = strlen(txt);
662 cmd = (len + 1 < otmp->onamelth) ? txt + len + 1 : (char *) 0;
663 if (!cmd || !*cmd) cmd = "SPAWN";
665 sprintf(qbuf, "System command (%s)", cmd);
666 getlin(qbuf, buf);
667 if (*buf != '\033') {
668 for (p = eos(buf); p > buf; *p = '\0')
669 if (*--p != ' ') break; /* strip trailing spaces */
670 if (*buf) cmd = buf; /* use user entered command */
671 if (!strcmpi(cmd, "SPAWN") || !strcmp(cmd, "!"))
672 cmd = (char *) 0; /* interactive escape */
674 vms_doshell(cmd, TRUE);
675 (void) sleep(1);
677 # endif /* SHELL */
680 # endif /* VMS */
682 # ifdef LAN_MAIL
684 void
685 ckmailstatus()
687 static int laststattime = 0;
689 if(u.uswallow || !flags.biff
690 || moves < laststattime + mailckfreq)
691 return;
693 laststattime = moves;
694 if (lan_mail_check()) {
695 static struct mail_info deliver = {
696 # ifndef NO_MAILREADER
697 MSG_MAIL, "I have some mail for you",
698 # else
699 /* suppress creation and delivery of scroll of mail */
700 MSG_OTHER, "You have some mail in the outside world",
701 # endif
702 0, 0
704 newmail(&deliver);
708 /*ARGSUSED*/
709 void
710 readmail(otmp)
711 struct obj *otmp;
713 lan_mail_read(otmp);
716 # endif /* LAN_MAIL */
718 #endif /* OVL0 */
720 #endif /* MAIL */
722 /*mail.c*/