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