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