OSDN Git Service

import nethack-3.6.0
[jnethack/source.git] / src / mail.c
1 /* NetHack 3.6  mail.c  $NHDT-Date: 1436754892 2015/07/13 02:34:52 $  $NHDT-Branch: master $:$NHDT-Revision: 1.20 $ */
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
24  *read.
25  *      - Do something to the text when the scroll is enchanted or cancelled.
26  *      - Make the daemon always appear at a stairwell, and have it find a
27  *        path to the hero.
28  *
29  * Note by Olaf Seibert: On the Amiga, we usually don't get mail.  So we go
30  *                       through most of the effects at 'random' moments.
31  * Note by Paul Winner:  The MSDOS port also 'fakes' the mail daemon at
32  *                       random intervals.
33  */
34
35 STATIC_DCL boolean FDECL(md_start, (coord *));
36 STATIC_DCL boolean FDECL(md_stop, (coord *, coord *));
37 STATIC_DCL boolean FDECL(md_rush, (struct monst *, int, int));
38 STATIC_DCL void FDECL(newmail, (struct mail_info *));
39
40 extern char *viz_rmin, *viz_rmax; /* line-of-sight limits (vision.c) */
41
42 #if !defined(UNIX) && !defined(VMS)
43 int mustgetmail = -1;
44 #endif
45
46 #ifdef UNIX
47 #include <sys/stat.h>
48 #include <pwd.h>
49 /* DON'T trust all Unices to declare getpwuid() in <pwd.h> */
50 #if !defined(_BULL_SOURCE) && !defined(__sgi) && !defined(_M_UNIX)
51 #if !defined(SUNOS4) && !(defined(ULTRIX) && defined(__GNUC__))
52 /* DO trust all SVR4 to typedef uid_t in <sys/types.h> (probably to a long) */
53 #if defined(POSIX_TYPES) || defined(SVR4) || defined(HPUX)
54 extern struct passwd *FDECL(getpwuid, (uid_t));
55 #else
56 extern struct passwd *FDECL(getpwuid, (int));
57 #endif
58 #endif
59 #endif
60 static struct stat omstat, nmstat;
61 static char *mailbox = (char *) 0;
62 static long laststattime;
63
64 #if !defined(MAILPATH) && defined(AMS) /* Just a placeholder for AMS */
65 #define MAILPATH "/dev/null"
66 #endif
67 #if !defined(MAILPATH) && (defined(LINUX) || defined(__osf__))
68 #define MAILPATH "/var/spool/mail/"
69 #endif
70 #if !defined(MAILPATH) && defined(__FreeBSD__)
71 #define MAILPATH "/var/mail/"
72 #endif
73 #if !defined(MAILPATH) && (defined(BSD) || defined(ULTRIX))
74 #define MAILPATH "/usr/spool/mail/"
75 #endif
76 #if !defined(MAILPATH) && (defined(SYSV) || defined(HPUX))
77 #define MAILPATH "/usr/mail/"
78 #endif
79
80 void
81 getmailstatus()
82 {
83     if (!mailbox && !(mailbox = nh_getenv("MAIL"))) {
84 #ifdef MAILPATH
85 #ifdef AMS
86         struct passwd ppasswd;
87
88         (void) memcpy(&ppasswd, getpwuid(getuid()), sizeof(struct passwd));
89         if (ppasswd.pw_dir) {
90             mailbox = (char *) alloc((unsigned) strlen(ppasswd.pw_dir)
91                                      + sizeof(AMS_MAILBOX));
92             Strcpy(mailbox, ppasswd.pw_dir);
93             Strcat(mailbox, AMS_MAILBOX);
94         } else
95             return;
96 #else
97         const char *pw_name = getpwuid(getuid())->pw_name;
98         mailbox = (char *) alloc(sizeof(MAILPATH) + strlen(pw_name));
99         Strcpy(mailbox, MAILPATH);
100         Strcat(mailbox, pw_name);
101 #endif /* AMS */
102 #else
103         return;
104 #endif
105     }
106     if (stat(mailbox, &omstat)) {
107 #ifdef PERMANENT_MAILBOX
108         pline("Cannot get status of MAIL=\"%s\".", mailbox);
109         mailbox = 0;
110 #else
111         omstat.st_mtime = 0;
112 #endif
113     }
114 }
115 #endif /* UNIX */
116
117 /*
118  * Pick coordinates for a starting position for the mail daemon.  Called
119  * from newmail() and newphone().
120  */
121 STATIC_OVL boolean
122 md_start(startp)
123 coord *startp;
124 {
125     coord testcc;     /* scratch coordinates */
126     int row;          /* current row we are checking */
127     int lax;          /* if TRUE, pick a position in sight. */
128     int dd;           /* distance to current point */
129     int max_distance; /* max distance found so far */
130
131     /*
132      * If blind and not telepathic, then it doesn't matter what we pick ---
133      * the hero is not going to see it anyway.  So pick a nearby position.
134      */
135     if (Blind && !Blind_telepat) {
136         if (!enexto(startp, u.ux, u.uy, (struct permonst *) 0))
137             return FALSE; /* no good positions */
138         return TRUE;
139     }
140
141     /*
142      * Arrive at an up or down stairwell if it is in line of sight from the
143      * hero.
144      */
145     if (couldsee(upstair.sx, upstair.sy)) {
146         startp->x = upstair.sx;
147         startp->y = upstair.sy;
148         return TRUE;
149     }
150     if (couldsee(dnstair.sx, dnstair.sy)) {
151         startp->x = dnstair.sx;
152         startp->y = dnstair.sy;
153         return TRUE;
154     }
155
156     /*
157      * Try to pick a location out of sight next to the farthest position away
158      * from the hero.  If this fails, try again, just picking the farthest
159      * position that could be seen.  What we really ought to be doing is
160      * finding a path from a stairwell...
161      *
162      * The arrays viz_rmin[] and viz_rmax[] are set even when blind.  These
163      * are the LOS limits for each row.
164      */
165     lax = 0; /* be picky */
166     max_distance = -1;
167 retry:
168     for (row = 0; row < ROWNO; row++) {
169         if (viz_rmin[row] < viz_rmax[row]) {
170             /* There are valid positions on this row. */
171             dd = distu(viz_rmin[row], row);
172             if (dd > max_distance) {
173                 if (lax) {
174                     max_distance = dd;
175                     startp->y = row;
176                     startp->x = viz_rmin[row];
177
178                 } else if (enexto(&testcc, (xchar) viz_rmin[row], row,
179                                   (struct permonst *) 0)
180                            && !cansee(testcc.x, testcc.y)
181                            && couldsee(testcc.x, testcc.y)) {
182                     max_distance = dd;
183                     *startp = testcc;
184                 }
185             }
186             dd = distu(viz_rmax[row], row);
187             if (dd > max_distance) {
188                 if (lax) {
189                     max_distance = dd;
190                     startp->y = row;
191                     startp->x = viz_rmax[row];
192
193                 } else if (enexto(&testcc, (xchar) viz_rmax[row], row,
194                                   (struct permonst *) 0)
195                            && !cansee(testcc.x, testcc.y)
196                            && couldsee(testcc.x, testcc.y)) {
197                     max_distance = dd;
198                     *startp = testcc;
199                 }
200             }
201         }
202     }
203
204     if (max_distance < 0) {
205         if (!lax) {
206             lax = 1; /* just find a position */
207             goto retry;
208         }
209         return FALSE;
210     }
211
212     return TRUE;
213 }
214
215 /*
216  * Try to choose a stopping point as near as possible to the starting
217  * position while still adjacent to the hero.  If all else fails, try
218  * enexto().  Use enexto() as a last resort because enexto() chooses
219  * its point randomly, which is not what we want.
220  */
221 STATIC_OVL boolean
222 md_stop(stopp, startp)
223 coord *stopp;  /* stopping position (we fill it in) */
224 coord *startp; /* starting position (read only) */
225 {
226     int x, y, distance, min_distance = -1;
227
228     for (x = u.ux - 1; x <= u.ux + 1; x++)
229         for (y = u.uy - 1; y <= u.uy + 1; y++) {
230             if (!isok(x, y) || (x == u.ux && y == u.uy))
231                 continue;
232
233             if (accessible(x, y) && !MON_AT(x, y)) {
234                 distance = dist2(x, y, startp->x, startp->y);
235                 if (min_distance < 0 || distance < min_distance
236                     || (distance == min_distance && rn2(2))) {
237                     stopp->x = x;
238                     stopp->y = y;
239                     min_distance = distance;
240                 }
241             }
242         }
243
244     /* If we didn't find a good spot, try enexto(). */
245     if (min_distance < 0 && !enexto(stopp, u.ux, u.uy, &mons[PM_MAIL_DAEMON]))
246         return FALSE;
247
248     return TRUE;
249 }
250
251 /* Let the mail daemon have a larger vocabulary. */
252 static NEARDATA const char *mail_text[] = { "Gangway!", "Look out!",
253                                             "Pardon me!" };
254 #define md_exclamations() (mail_text[rn2(3)])
255
256 /*
257  * Make the mail daemon run through the dungeon.  The daemon will run over
258  * any monsters that are in its path, but will replace them later.  Return
259  * FALSE if the md gets stuck in a position where there is a monster.  Return
260  * TRUE otherwise.
261  */
262 STATIC_OVL boolean
263 md_rush(md, tx, ty)
264 struct monst *md;
265 register int tx, ty; /* destination of mail daemon */
266 {
267     struct monst *mon;            /* displaced monster */
268     register int dx, dy;          /* direction counters */
269     int fx = md->mx, fy = md->my; /* current location */
270     int nfx = fx, nfy = fy,       /* new location */
271         d1, d2;                   /* shortest distances */
272
273     /*
274      * It is possible that the monster at (fx,fy) is not the md when:
275      * the md rushed the hero and failed, and is now starting back.
276      */
277     if (m_at(fx, fy) == md) {
278         remove_monster(fx, fy); /* pick up from orig position */
279         newsym(fx, fy);
280     }
281
282     /*
283      * At the beginning and exit of this loop, md is not placed in the
284      * dungeon.
285      */
286     while (1) {
287         /* Find a good location next to (fx,fy) closest to (tx,ty). */
288         d1 = dist2(fx, fy, tx, ty);
289         for (dx = -1; dx <= 1; dx++)
290             for (dy = -1; dy <= 1; dy++)
291                 if ((dx || dy) && isok(fx + dx, fy + dy)
292                     && !IS_STWALL(levl[fx + dx][fy + dy].typ)) {
293                     d2 = dist2(fx + dx, fy + dy, tx, ty);
294                     if (d2 < d1) {
295                         d1 = d2;
296                         nfx = fx + dx;
297                         nfy = fy + dy;
298                     }
299                 }
300
301         /* Break if the md couldn't find a new position. */
302         if (nfx == fx && nfy == fy)
303             break;
304
305         fx = nfx; /* this is our new position */
306         fy = nfy;
307
308         /* Break if the md reaches its destination. */
309         if (fx == tx && fy == ty)
310             break;
311
312         if ((mon = m_at(fx, fy)) != 0) /* save monster at this position */
313             verbalize1(md_exclamations());
314         else if (fx == u.ux && fy == u.uy)
315             verbalize("Excuse me.");
316
317         place_monster(md, fx, fy); /* put md down */
318         newsym(fx, fy);            /* see it */
319         flush_screen(0);           /* make sure md shows up */
320         delay_output();            /* wait a little bit */
321
322         /* Remove md from the dungeon.  Restore original mon, if necessary. */
323         if (mon) {
324             if ((mon->mx != fx) || (mon->my != fy))
325                 place_worm_seg(mon, fx, fy);
326             else
327                 place_monster(mon, fx, fy);
328         } else
329             remove_monster(fx, fy);
330         newsym(fx, fy);
331     }
332
333     /*
334      * Check for a monster at our stopping position (this is possible, but
335      * very unlikely).  If one exists, then have the md leave in disgust.
336      */
337     if ((mon = m_at(fx, fy)) != 0) {
338         place_monster(md, fx, fy); /* display md with text below */
339         newsym(fx, fy);
340         verbalize("This place's too crowded.  I'm outta here.");
341
342         if ((mon->mx != fx) || (mon->my != fy)) /* put mon back */
343             place_worm_seg(mon, fx, fy);
344         else
345             place_monster(mon, fx, fy);
346
347         newsym(fx, fy);
348         return FALSE;
349     }
350
351     place_monster(md, fx, fy); /* place at final spot */
352     newsym(fx, fy);
353     flush_screen(0);
354     delay_output(); /* wait a little bit */
355
356     return TRUE;
357 }
358
359 /* Deliver a scroll of mail. */
360 /*ARGSUSED*/
361 STATIC_OVL void
362 newmail(info)
363 struct mail_info *info;
364 {
365     struct monst *md;
366     coord start, stop;
367     boolean message_seen = FALSE;
368
369     /* Try to find good starting and stopping places. */
370     if (!md_start(&start) || !md_stop(&stop, &start))
371         goto give_up;
372
373     /* Make the daemon.  Have it rush towards the hero. */
374     if (!(md = makemon(&mons[PM_MAIL_DAEMON], start.x, start.y, NO_MM_FLAGS)))
375         goto give_up;
376     if (!md_rush(md, stop.x, stop.y))
377         goto go_back;
378
379     message_seen = TRUE;
380     verbalize("%s, %s!  %s.", Hello(md), plname, info->display_txt);
381
382     if (info->message_typ) {
383         struct obj *obj = mksobj(SCR_MAIL, FALSE, FALSE);
384
385         if (info->object_nam)
386             obj = oname(obj, info->object_nam);
387         if (info->response_cmd)
388             new_omailcmd(obj, info->response_cmd);
389
390         if (distu(md->mx, md->my) > 2)
391             verbalize("Catch!");
392         display_nhwindow(WIN_MESSAGE, FALSE);
393         obj = hold_another_object(obj, "Oops!", (const char *) 0,
394                                   (const char *) 0);
395     }
396
397 /* zip back to starting location */
398 go_back:
399     (void) md_rush(md, start.x, start.y);
400     mongone(md);
401 /* deliver some classes of messages even if no daemon ever shows up */
402 give_up:
403     if (!message_seen && info->message_typ == MSG_OTHER)
404         pline("Hark!  \"%s.\"", info->display_txt);
405 }
406
407 #if !defined(UNIX) && !defined(VMS)
408
409 void
410 ckmailstatus()
411 {
412     if (u.uswallow || !flags.biff)
413         return;
414     if (mustgetmail < 0) {
415 #if defined(AMIGA) || defined(MSDOS) || defined(TOS)
416         mustgetmail = (moves < 2000) ? (100 + rn2(2000)) : (2000 + rn2(3000));
417 #endif
418         return;
419     }
420     if (--mustgetmail <= 0) {
421         static struct mail_info deliver = { MSG_MAIL,
422                                             "I have some mail for you", 0,
423                                             0 };
424         newmail(&deliver);
425         mustgetmail = -1;
426     }
427 }
428
429 /*ARGSUSED*/
430 void
431 readmail(otmp)
432 struct obj *otmp;
433 {
434     static char *junk[] = {
435         NULL, /* placeholder for "Report bugs to <devteam@nethack.org>.", */
436         "Please disregard previous letter.", "Welcome to NetHack.",
437 #ifdef AMIGA
438         "Only Amiga makes it possible.", "CATS have all the answers.",
439 #endif
440         "This mail complies with the Yendorian Anti-Spam Act (YASA)",
441         "Please find enclosed a small token to represent your Owlbear",
442         "**FR33 P0T10N 0F FULL H34L1NG**",
443         "Please return to sender (Asmodeus)",
444       "Buy a potion of gain level for only $19.99! Guaranteed to be blessed!",
445         "Invitation: Visit the NetHack web site at http://www.nethack.org!"
446     };
447
448     /* XXX replace with more general substitution code and add local
449      * contact message.  Also use DEVTEAM_URL */
450     if (junk[0] == NULL) {
451 #define BUGS_FORMAT "Report bugs to <%s>."
452         /* +2 from '%s' suffices as substitute for usual +1 for terminator */
453         junk[0] = (char *) alloc(strlen(BUGS_FORMAT) + strlen(DEVTEAM_EMAIL));
454         Sprintf(junk[0], BUGS_FORMAT, DEVTEAM_EMAIL);
455 #undef BUGS_FORMAT
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 #endif /* !UNIX && !VMS */
464
465 #ifdef UNIX
466
467 void
468 ckmailstatus()
469 {
470     if (!mailbox || u.uswallow || !flags.biff
471 #ifdef MAILCKFREQ
472         || moves < laststattime + MAILCKFREQ
473 #endif
474         )
475         return;
476
477     laststattime = moves;
478     if (stat(mailbox, &nmstat)) {
479 #ifdef PERMANENT_MAILBOX
480         pline("Cannot get status of MAIL=\"%s\" anymore.", mailbox);
481         mailbox = 0;
482 #else
483         nmstat.st_mtime = 0;
484 #endif
485     } else if (nmstat.st_mtime > omstat.st_mtime) {
486         if (nmstat.st_size) {
487             static struct mail_info deliver = {
488 #ifndef NO_MAILREADER
489                 MSG_MAIL, "I have some mail for you",
490 #else
491                 /* suppress creation and delivery of scroll of mail */
492                 MSG_OTHER, "You have some mail in the outside world",
493 #endif
494                 0, 0
495             };
496             newmail(&deliver);
497         }
498         getmailstatus(); /* might be too late ... */
499     }
500 }
501
502 /*ARGSUSED*/
503 void
504 readmail(otmp)
505 struct obj *otmp;
506 {
507 #ifdef DEF_MAILREADER /* This implies that UNIX is defined */
508     register const char *mr = 0;
509
510     display_nhwindow(WIN_MESSAGE, FALSE);
511     if (!(mr = nh_getenv("MAILREADER")))
512         mr = DEF_MAILREADER;
513
514     if (child(1)) {
515         (void) execl(mr, mr, (char *) 0);
516         terminate(EXIT_FAILURE);
517     }
518 #else
519 #ifndef AMS /* AMS mailboxes are directories */
520     display_file(mailbox, TRUE);
521 #endif      /* AMS */
522 #endif      /* DEF_MAILREADER */
523
524     /* get new stat; not entirely correct: there is a small time
525        window where we do not see new mail */
526     getmailstatus();
527 }
528
529 #endif /* UNIX */
530
531 #ifdef VMS
532
533 extern NDECL(struct mail_info *parse_next_broadcast);
534
535 volatile int broadcasts = 0;
536
537 void
538 ckmailstatus()
539 {
540     struct mail_info *brdcst;
541
542     if (u.uswallow || !flags.biff)
543         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
554 void
555 readmail(otmp)
556 struct 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 in OMAILCMD */
564     if (has_oname(otmp))
565         txt = ONAME(otmp);
566     else
567         txt = "";
568     len = strlen(txt);
569     if (has_omailcmd(otmp))
570         cmd = OMAILCMD(otmp);
571     if (!cmd || !*cmd)
572         cmd = "SPAWN";
573
574     Sprintf(qbuf, "System command (%s)", cmd);
575     getlin(qbuf, buf);
576     if (*buf != '\033') {
577         for (p = eos(buf); p > buf; *p = '\0')
578             if (*--p != ' ')
579                 break; /* strip trailing spaces */
580         if (*buf)
581             cmd = buf; /* use user entered command */
582         if (!strcmpi(cmd, "SPAWN") || !strcmp(cmd, "!"))
583             cmd = (char *) 0; /* interactive escape */
584
585         vms_doshell(cmd, TRUE);
586         (void) sleep(1);
587     }
588 #endif /* SHELL */
589 }
590
591 #endif /* VMS */
592 #endif /* MAIL */
593
594 /*mail.c*/