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