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