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