OSDN Git Service

fix #36223
[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 /* JNetHack Copyright */
6 /* (c) Issei Numata, Naoki Hamada, Shigehiro Miyashita, 1994-2000  */
7 /* For 3.4-, Copyright (c) SHIRAKATA Kentaro, 2002-2016            */
8 /* JNetHack may be freely redistributed.  See license for details. */
9
10 #include "hack.h"
11
12 #ifdef MAIL
13 #include "mail.h"
14
15 /*
16  * Notify user when new mail has arrived.  Idea by Merlyn Leroy.
17  *
18  * The mail daemon can move with less than usual restraint.  It can:
19  *      - move diagonally from a door
20  *      - use secret and closed doors
21  *      - run through a monster ("Gangway!", etc.)
22  *      - run over pools & traps
23  *
24  * Possible extensions:
25  *      - Open the file MAIL and do fstat instead of stat for efficiency.
26  *        (But sh uses stat, so this cannot be too bad.)
27  *      - Examine the mail and produce a scroll of mail named "From somebody".
28  *      - Invoke MAILREADER in such a way that only this single letter is
29  *read.
30  *      - Do something to the text when the scroll is enchanted or cancelled.
31  *      - Make the daemon always appear at a stairwell, and have it find a
32  *        path to the hero.
33  *
34  * Note by Olaf Seibert: On the Amiga, we usually don't get mail.  So we go
35  *                       through most of the effects at 'random' moments.
36  * Note by Paul Winner:  The MSDOS port also 'fakes' the mail daemon at
37  *                       random intervals.
38  */
39
40 STATIC_DCL boolean FDECL(md_start, (coord *));
41 STATIC_DCL boolean FDECL(md_stop, (coord *, coord *));
42 STATIC_DCL boolean FDECL(md_rush, (struct monst *, int, int));
43 STATIC_DCL void FDECL(newmail, (struct mail_info *));
44
45 extern char *viz_rmin, *viz_rmax; /* line-of-sight limits (vision.c) */
46
47 #if !defined(UNIX) && !defined(VMS)
48 int mustgetmail = -1;
49 #endif
50
51 #ifdef UNIX
52 #include <sys/stat.h>
53 #include <pwd.h>
54 /* DON'T trust all Unices to declare getpwuid() in <pwd.h> */
55 #if !defined(_BULL_SOURCE) && !defined(__sgi) && !defined(_M_UNIX)
56 #if !defined(SUNOS4) && !(defined(ULTRIX) && defined(__GNUC__))
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 static struct stat omstat, nmstat;
66 static char *mailbox = (char *) 0;
67 static long laststattime;
68
69 #if !defined(MAILPATH) && defined(AMS) /* Just a placeholder for AMS */
70 #define MAILPATH "/dev/null"
71 #endif
72 #if !defined(MAILPATH) && (defined(LINUX) || defined(__osf__))
73 #define MAILPATH "/var/spool/mail/"
74 #endif
75 #if !defined(MAILPATH) && defined(__FreeBSD__)
76 #define MAILPATH "/var/mail/"
77 #endif
78 #if !defined(MAILPATH) && (defined(BSD) || defined(ULTRIX))
79 #define MAILPATH "/usr/spool/mail/"
80 #endif
81 #if !defined(MAILPATH) && (defined(SYSV) || defined(HPUX))
82 #define MAILPATH "/usr/mail/"
83 #endif
84
85 void
86 getmailstatus()
87 {
88     if (!mailbox && !(mailbox = nh_getenv("MAIL"))) {
89 #ifdef MAILPATH
90 #ifdef AMS
91         struct passwd ppasswd;
92
93         (void) memcpy(&ppasswd, getpwuid(getuid()), sizeof(struct passwd));
94         if (ppasswd.pw_dir) {
95             mailbox = (char *) alloc((unsigned) strlen(ppasswd.pw_dir)
96                                      + 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 /*
123  * Pick coordinates for a starting position for the mail daemon.  Called
124  * from newmail() and newphone().
125  */
126 STATIC_OVL boolean
127 md_start(startp)
128 coord *startp;
129 {
130     coord testcc;     /* scratch coordinates */
131     int row;          /* current row we are checking */
132     int lax;          /* if TRUE, pick a position in sight. */
133     int dd;           /* distance to current point */
134     int max_distance; /* max distance found so far */
135
136     /*
137      * If blind and not telepathic, then it doesn't matter what we pick ---
138      * the hero is not going to see it anyway.  So pick a nearby position.
139      */
140     if (Blind && !Blind_telepat) {
141         if (!enexto(startp, u.ux, u.uy, (struct permonst *) 0))
142             return FALSE; /* no good positions */
143         return TRUE;
144     }
145
146     /*
147      * Arrive at an up or down stairwell if it is in line of sight from the
148      * hero.
149      */
150     if (couldsee(upstair.sx, upstair.sy)) {
151         startp->x = upstair.sx;
152         startp->y = upstair.sy;
153         return TRUE;
154     }
155     if (couldsee(dnstair.sx, dnstair.sy)) {
156         startp->x = dnstair.sx;
157         startp->y = dnstair.sy;
158         return TRUE;
159     }
160
161     /*
162      * Try to pick a location out of sight next to the farthest position away
163      * from the hero.  If this fails, try again, just picking the farthest
164      * position that could be seen.  What we really ought to be doing is
165      * finding a path from a stairwell...
166      *
167      * The arrays viz_rmin[] and viz_rmax[] are set even when blind.  These
168      * are the LOS limits for each row.
169      */
170     lax = 0; /* be picky */
171     max_distance = -1;
172 retry:
173     for (row = 0; row < ROWNO; row++) {
174         if (viz_rmin[row] < viz_rmax[row]) {
175             /* There are valid positions on this row. */
176             dd = distu(viz_rmin[row], row);
177             if (dd > max_distance) {
178                 if (lax) {
179                     max_distance = dd;
180                     startp->y = row;
181                     startp->x = viz_rmin[row];
182
183                 } else if (enexto(&testcc, (xchar) viz_rmin[row], row,
184                                   (struct permonst *) 0)
185                            && !cansee(testcc.x, testcc.y)
186                            && couldsee(testcc.x, testcc.y)) {
187                     max_distance = dd;
188                     *startp = testcc;
189                 }
190             }
191             dd = distu(viz_rmax[row], row);
192             if (dd > max_distance) {
193                 if (lax) {
194                     max_distance = dd;
195                     startp->y = row;
196                     startp->x = viz_rmax[row];
197
198                 } else if (enexto(&testcc, (xchar) viz_rmax[row], row,
199                                   (struct permonst *) 0)
200                            && !cansee(testcc.x, testcc.y)
201                            && couldsee(testcc.x, testcc.y)) {
202                     max_distance = dd;
203                     *startp = testcc;
204                 }
205             }
206         }
207     }
208
209     if (max_distance < 0) {
210         if (!lax) {
211             lax = 1; /* just find a position */
212             goto retry;
213         }
214         return FALSE;
215     }
216
217     return TRUE;
218 }
219
220 /*
221  * Try to choose a stopping point as near as possible to the starting
222  * position while still adjacent to the hero.  If all else fails, try
223  * enexto().  Use enexto() as a last resort because enexto() chooses
224  * its point randomly, which is not what we want.
225  */
226 STATIC_OVL boolean
227 md_stop(stopp, startp)
228 coord *stopp;  /* stopping position (we fill it in) */
229 coord *startp; /* starting position (read only) */
230 {
231     int x, y, distance, min_distance = -1;
232
233     for (x = u.ux - 1; x <= u.ux + 1; x++)
234         for (y = u.uy - 1; y <= u.uy + 1; y++) {
235             if (!isok(x, y) || (x == u.ux && y == u.uy))
236                 continue;
237
238             if (accessible(x, y) && !MON_AT(x, y)) {
239                 distance = dist2(x, y, startp->x, startp->y);
240                 if (min_distance < 0 || distance < min_distance
241                     || (distance == min_distance && rn2(2))) {
242                     stopp->x = x;
243                     stopp->y = y;
244                     min_distance = distance;
245                 }
246             }
247         }
248
249     /* If we didn't find a good spot, try enexto(). */
250     if (min_distance < 0 && !enexto(stopp, u.ux, u.uy, &mons[PM_MAIL_DAEMON]))
251         return FALSE;
252
253     return TRUE;
254 }
255
256 /* Let the mail daemon have a larger vocabulary. */
257 #if 0 /*JP*/
258 static NEARDATA const char *mail_text[] = { "Gangway!", "Look out!",
259                                             "Pardon me!" };
260 #else
261 static NEARDATA const char *mail_text[] = { "\82Ç\82¢\82½\82Ç\82¢\82½\81I", "\8bC\82ð\82Â\82¯\82ë\81I",
262                                             "\82\82á\82Ü\82·\82é\82æ\81I" };
263 #endif
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++)
300             for (dy = -1; dy <= 1; dy++)
301                 if ((dx || dy) && isok(fx + dx, fy + dy)
302                     && !IS_STWALL(levl[fx + dx][fy + dy].typ)) {
303                     d2 = dist2(fx + dx, fy + dy, tx, ty);
304                     if (d2 < d1) {
305                         d1 = d2;
306                         nfx = fx + dx;
307                         nfy = fy + dy;
308                     }
309                 }
310
311         /* Break if the md couldn't find a new position. */
312         if (nfx == fx && nfy == fy)
313             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)
320             break;
321
322         if ((mon = m_at(fx, fy)) != 0) /* save monster at this position */
323             verbalize1(md_exclamations());
324         else if (fx == u.ux && fy == u.uy)
325 /*JP
326             verbalize("Excuse me.");
327 */
328             verbalize("\82¿\82å\82Á\82Æ\82µ\82Â\82ê\82¢\81D");
329
330         place_monster(md, fx, fy); /* put md down */
331         newsym(fx, fy);            /* see it */
332         flush_screen(0);           /* make sure md shows up */
333         delay_output();            /* wait a little bit */
334
335         /* Remove md from the dungeon.  Restore original mon, if necessary. */
336         if (mon) {
337             if ((mon->mx != fx) || (mon->my != fy))
338                 place_worm_seg(mon, fx, fy);
339             else
340                 place_monster(mon, fx, fy);
341         } else
342             remove_monster(fx, fy);
343         newsym(fx, fy);
344     }
345
346     /*
347      * Check for a monster at our stopping position (this is possible, but
348      * very unlikely).  If one exists, then have the md leave in disgust.
349      */
350     if ((mon = m_at(fx, fy)) != 0) {
351         place_monster(md, fx, fy); /* display md with text below */
352         newsym(fx, fy);
353 /*JP
354         verbalize("This place's too crowded.  I'm outta here.");
355 */
356         verbalize("\82±\82±\82Í\8d¬\82Ý\82·\82¬\81D\82±\82±\82Å\91Ò\82Á\82Ä\82é\82æ\81D");
357
358         if ((mon->mx != fx) || (mon->my != fy)) /* put mon back */
359             place_worm_seg(mon, fx, fy);
360         else
361             place_monster(mon, fx, fy);
362
363         newsym(fx, fy);
364         return FALSE;
365     }
366
367     place_monster(md, fx, fy); /* place at final spot */
368     newsym(fx, fy);
369     flush_screen(0);
370     delay_output(); /* wait a little bit */
371
372     return TRUE;
373 }
374
375 /* Deliver a scroll of mail. */
376 /*ARGSUSED*/
377 STATIC_OVL void
378 newmail(info)
379 struct mail_info *info;
380 {
381     struct monst *md;
382     coord start, stop;
383     boolean message_seen = FALSE;
384
385     /* Try to find good starting and stopping places. */
386     if (!md_start(&start) || !md_stop(&stop, &start))
387         goto give_up;
388
389     /* Make the daemon.  Have it rush towards the hero. */
390     if (!(md = makemon(&mons[PM_MAIL_DAEMON], start.x, start.y, NO_MM_FLAGS)))
391         goto give_up;
392     if (!md_rush(md, stop.x, stop.y))
393         goto go_back;
394
395     message_seen = TRUE;
396 #if 0 /*JP*/
397     verbalize("%s, %s!  %s.", Hello(md), plname, info->display_txt);
398 #else
399     verbalize("%s\81I%s\81D", Hello(md), info->display_txt);
400 #endif
401
402     if (info->message_typ) {
403         struct obj *obj = mksobj(SCR_MAIL, FALSE, FALSE);
404
405         if (info->object_nam)
406             obj = oname(obj, info->object_nam);
407         if (info->response_cmd)
408             new_omailcmd(obj, info->response_cmd);
409
410         if (distu(md->mx, md->my) > 2)
411 /*JP
412             verbalize("Catch!");
413 */
414             verbalize("\82Ù\82ç\82æ\81I");
415         display_nhwindow(WIN_MESSAGE, FALSE);
416 #if 0 /*JP*/
417         obj = hold_another_object(obj, "Oops!", (const char *) 0,
418                                   (const char *) 0);
419 #else
420         obj = hold_another_object(obj, "\82¨\82Á\82Æ\81I", (const char *) 0,
421                                   (const char *) 0);
422 #endif
423     }
424
425 /* zip back to starting location */
426 go_back:
427     (void) md_rush(md, start.x, start.y);
428     mongone(md);
429 /* deliver some classes of messages even if no daemon ever shows up */
430 give_up:
431     if (!message_seen && info->message_typ == MSG_OTHER)
432 /*JP
433         pline("Hark!  \"%s.\"", info->display_txt);
434 */
435         pline("\81u%s\81D\81v\82Æ\8c¾\82¤\82±\82Æ\82¾\81I", info->display_txt);
436 }
437
438 #if !defined(UNIX) && !defined(VMS)
439
440 void
441 ckmailstatus()
442 {
443     if (u.uswallow || !flags.biff)
444         return;
445     if (mustgetmail < 0) {
446 #if defined(AMIGA) || defined(MSDOS) || defined(TOS)
447         mustgetmail = (moves < 2000) ? (100 + rn2(2000)) : (2000 + rn2(3000));
448 #endif
449         return;
450     }
451     if (--mustgetmail <= 0) {
452         static struct mail_info deliver = { MSG_MAIL,
453 /*JP
454                                             "I have some mail for you", 0,
455 */
456                                             "\83\81\81[\83\8b\82ð\8e\9d\82Á\82Ä\82«\82½\82æ", 0,
457                                             0 };
458         newmail(&deliver);
459         mustgetmail = -1;
460     }
461 }
462
463 /*ARGSUSED*/
464 void
465 readmail(otmp)
466 struct obj *otmp;
467 {
468     static char *junk[] = {
469         NULL, /* placeholder for "Report bugs to <devteam@nethack.org>.", */
470 /*JP
471         "Please disregard previous letter.", "Welcome to NetHack.",
472 */
473         "\91O\82Ì\83\81\81[\83\8b\82Í\96Y\82ê\82Ä\82­\82¾\82³\82¢\81D", "NetHack\82Ö\82æ\82¤\82±\82»\81I",
474 #ifdef AMIGA
475         "Only Amiga makes it possible.", "CATS have all the answers.",
476 #endif
477         "This mail complies with the Yendorian Anti-Spam Act (YASA)",
478         "Please find enclosed a small token to represent your Owlbear",
479         "**FR33 P0T10N 0F FULL H34L1NG**",
480         "Please return to sender (Asmodeus)",
481       "Buy a potion of gain level for only $19.99! Guaranteed to be blessed!",
482         "Invitation: Visit the NetHack web site at http://www.nethack.org!"
483     };
484
485     /* XXX replace with more general substitution code and add local
486      * contact message.  Also use DEVTEAM_URL */
487     if (junk[0] == NULL) {
488 #define BUGS_FORMAT "Report bugs to <%s>."
489         /* +2 from '%s' suffices as substitute for usual +1 for terminator */
490         junk[0] = (char *) alloc(strlen(BUGS_FORMAT) + strlen(DEVTEAM_EMAIL));
491         Sprintf(junk[0], BUGS_FORMAT, DEVTEAM_EMAIL);
492 #undef BUGS_FORMAT
493     }
494     if (Blind) {
495 /*JP
496         pline("Unfortunately you cannot see what it says.");
497 */
498         pline("\8ec\94O\82È\82ª\82ç\89½\82Æ\8f\91\82¢\82Ä\82 \82é\82Ì\82©\8c©\82é\82±\82Æ\82ª\82Å\82«\82È\82¢\81D");
499     } else
500 /*JP
501         pline("It reads:  \"%s\"", junk[rn2(SIZE(junk))]);
502 */
503         pline("\82»\82ê\82ð\93Ç\82ñ\82¾\81F\"%s\"", junk[rn2(SIZE(junk))]);
504 }
505
506 #endif /* !UNIX && !VMS */
507
508 #ifdef UNIX
509
510 void
511 ckmailstatus()
512 {
513     if (!mailbox || u.uswallow || !flags.biff
514 #ifdef MAILCKFREQ
515         || moves < laststattime + MAILCKFREQ
516 #endif
517         )
518         return;
519
520     laststattime = moves;
521     if (stat(mailbox, &nmstat)) {
522 #ifdef PERMANENT_MAILBOX
523         pline("Cannot get status of MAIL=\"%s\" anymore.", mailbox);
524         mailbox = 0;
525 #else
526         nmstat.st_mtime = 0;
527 #endif
528     } else if (nmstat.st_mtime > omstat.st_mtime) {
529         if (nmstat.st_size) {
530             static struct mail_info deliver = {
531 #ifndef NO_MAILREADER
532 /*JP
533                 MSG_MAIL, "I have some mail for you",
534 */
535                 MSG_MAIL, "\83\81\83C\83\8b\82ð\8e\9d\82Á\82Ä\82«\82½\82æ",
536 #else
537                 /* suppress creation and delivery of scroll of mail */
538 /*JP
539                 MSG_OTHER, "You have some mail in the outside world",
540 */
541                 MSG_OTHER, "\8aO\82Ì\90¢\8aE\82©\82ç\82Ì\83\81\81[\83\8b\82¾",
542 #endif
543                 0, 0
544             };
545             newmail(&deliver);
546         }
547         getmailstatus(); /* might be too late ... */
548     }
549 }
550
551 /*ARGSUSED*/
552 void
553 readmail(otmp)
554 struct obj *otmp;
555 {
556 #ifdef DEF_MAILREADER /* This implies that UNIX is defined */
557     register const char *mr = 0;
558
559     display_nhwindow(WIN_MESSAGE, FALSE);
560     if (!(mr = nh_getenv("MAILREADER")))
561         mr = DEF_MAILREADER;
562
563     if (child(1)) {
564         (void) execl(mr, mr, (char *) 0);
565         terminate(EXIT_FAILURE);
566     }
567 #else
568 #ifndef AMS /* AMS mailboxes are directories */
569     display_file(mailbox, TRUE);
570 #endif      /* AMS */
571 #endif      /* DEF_MAILREADER */
572
573     /* get new stat; not entirely correct: there is a small time
574        window where we do not see new mail */
575     getmailstatus();
576 }
577
578 #endif /* UNIX */
579
580 #ifdef VMS
581
582 extern NDECL(struct mail_info *parse_next_broadcast);
583
584 volatile int broadcasts = 0;
585
586 void
587 ckmailstatus()
588 {
589     struct mail_info *brdcst;
590
591     if (u.uswallow || !flags.biff)
592         return;
593
594     while (broadcasts > 0) { /* process all trapped broadcasts [until] */
595         broadcasts--;
596         if ((brdcst = parse_next_broadcast()) != 0) {
597             newmail(brdcst);
598             break; /* only handle one real message at a time */
599         }
600     }
601 }
602
603 void
604 readmail(otmp)
605 struct obj *otmp;
606 {
607 #ifdef SHELL /* can't access mail reader without spawning subprocess */
608     const char *txt, *cmd;
609     char *p, buf[BUFSZ], qbuf[BUFSZ];
610     int len;
611
612     /* there should be a command in OMAILCMD */
613     if (has_oname(otmp))
614         txt = ONAME(otmp);
615     else
616         txt = "";
617     len = strlen(txt);
618     if (has_omailcmd(otmp))
619         cmd = OMAILCMD(otmp);
620     if (!cmd || !*cmd)
621         cmd = "SPAWN";
622
623     Sprintf(qbuf, "System command (%s)", cmd);
624     getlin(qbuf, buf);
625     if (*buf != '\033') {
626         for (p = eos(buf); p > buf; *p = '\0')
627             if (*--p != ' ')
628                 break; /* strip trailing spaces */
629         if (*buf)
630             cmd = buf; /* use user entered command */
631         if (!strcmpi(cmd, "SPAWN") || !strcmp(cmd, "!"))
632             cmd = (char *) 0; /* interactive escape */
633
634         vms_doshell(cmd, TRUE);
635         (void) sleep(1);
636     }
637 #endif /* SHELL */
638 }
639
640 #endif /* VMS */
641 #endif /* MAIL */
642
643 /*mail.c*/