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
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
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
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
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
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
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
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
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
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
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
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
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
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*/